feat: Add dynamic layer system for Grid (closes #147)
Implements ColorLayer and TileLayer classes with z_index ordering: - ColorLayer: stores RGBA color per cell for overlays, fog of war, etc. - TileLayer: stores sprite index per cell with optional texture - z_index < 0: renders below entities - z_index >= 0: renders above entities Python API: - grid.add_layer(type, z_index, texture) - create layer - grid.remove_layer(layer) - remove layer - grid.layers - list of layers sorted by z_index - grid.layer(z_index) - get layer by z_index - layer.at(x,y) / layer.set(x,y,value) - cell access - layer.fill(value) - fill entire layer Layers are allocated separately from UIGridPoint, reducing memory for grids that don't need all features. Base grid retains walkable/ transparent arrays for TCOD pathfinding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f769c6c5f5
commit
4b05a95efe
6 changed files with 1342 additions and 19 deletions
621
src/GridLayers.cpp
Normal file
621
src/GridLayers.cpp
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
#include "GridLayers.h"
|
||||
#include "UIGrid.h"
|
||||
#include "PyColor.h"
|
||||
#include "PyTexture.h"
|
||||
#include <sstream>
|
||||
|
||||
// =============================================================================
|
||||
// GridLayer base class
|
||||
// =============================================================================
|
||||
|
||||
GridLayer::GridLayer(GridLayerType type, int z_index, int grid_x, int grid_y, UIGrid* parent)
|
||||
: type(type), z_index(z_index), grid_x(grid_x), grid_y(grid_y),
|
||||
parent_grid(parent), visible(true)
|
||||
{}
|
||||
|
||||
// =============================================================================
|
||||
// ColorLayer implementation
|
||||
// =============================================================================
|
||||
|
||||
ColorLayer::ColorLayer(int z_index, int grid_x, int grid_y, UIGrid* parent)
|
||||
: GridLayer(GridLayerType::Color, z_index, grid_x, grid_y, parent),
|
||||
colors(grid_x * grid_y, sf::Color::Transparent)
|
||||
{}
|
||||
|
||||
sf::Color& ColorLayer::at(int x, int y) {
|
||||
return colors[y * grid_x + x];
|
||||
}
|
||||
|
||||
const sf::Color& ColorLayer::at(int x, int y) const {
|
||||
return colors[y * grid_x + x];
|
||||
}
|
||||
|
||||
void ColorLayer::fill(const sf::Color& color) {
|
||||
std::fill(colors.begin(), colors.end(), color);
|
||||
}
|
||||
|
||||
void ColorLayer::resize(int new_grid_x, int new_grid_y) {
|
||||
std::vector<sf::Color> new_colors(new_grid_x * new_grid_y, sf::Color::Transparent);
|
||||
|
||||
// Copy existing data
|
||||
int copy_x = std::min(grid_x, new_grid_x);
|
||||
int copy_y = std::min(grid_y, new_grid_y);
|
||||
for (int y = 0; y < copy_y; ++y) {
|
||||
for (int x = 0; x < copy_x; ++x) {
|
||||
new_colors[y * new_grid_x + x] = colors[y * grid_x + x];
|
||||
}
|
||||
}
|
||||
|
||||
colors = std::move(new_colors);
|
||||
grid_x = new_grid_x;
|
||||
grid_y = new_grid_y;
|
||||
}
|
||||
|
||||
void ColorLayer::render(sf::RenderTarget& target,
|
||||
float left_spritepixels, float top_spritepixels,
|
||||
int left_edge, int top_edge, int x_limit, int y_limit,
|
||||
float zoom, int cell_width, int cell_height) {
|
||||
if (!visible) return;
|
||||
|
||||
sf::RectangleShape rect;
|
||||
rect.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom));
|
||||
rect.setOutlineThickness(0);
|
||||
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); x < x_limit; ++x) {
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); y < y_limit; ++y) {
|
||||
if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) continue;
|
||||
|
||||
const sf::Color& color = at(x, y);
|
||||
if (color.a == 0) continue; // Skip fully transparent
|
||||
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x * cell_width - left_spritepixels) * zoom,
|
||||
(y * cell_height - top_spritepixels) * zoom
|
||||
);
|
||||
|
||||
rect.setPosition(pixel_pos);
|
||||
rect.setFillColor(color);
|
||||
target.draw(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TileLayer implementation
|
||||
// =============================================================================
|
||||
|
||||
TileLayer::TileLayer(int z_index, int grid_x, int grid_y, UIGrid* parent,
|
||||
std::shared_ptr<PyTexture> texture)
|
||||
: GridLayer(GridLayerType::Tile, z_index, grid_x, grid_y, parent),
|
||||
tiles(grid_x * grid_y, -1), // -1 = no tile
|
||||
texture(texture)
|
||||
{}
|
||||
|
||||
int& TileLayer::at(int x, int y) {
|
||||
return tiles[y * grid_x + x];
|
||||
}
|
||||
|
||||
int TileLayer::at(int x, int y) const {
|
||||
return tiles[y * grid_x + x];
|
||||
}
|
||||
|
||||
void TileLayer::fill(int tile_index) {
|
||||
std::fill(tiles.begin(), tiles.end(), tile_index);
|
||||
}
|
||||
|
||||
void TileLayer::resize(int new_grid_x, int new_grid_y) {
|
||||
std::vector<int> new_tiles(new_grid_x * new_grid_y, -1);
|
||||
|
||||
// Copy existing data
|
||||
int copy_x = std::min(grid_x, new_grid_x);
|
||||
int copy_y = std::min(grid_y, new_grid_y);
|
||||
for (int y = 0; y < copy_y; ++y) {
|
||||
for (int x = 0; x < copy_x; ++x) {
|
||||
new_tiles[y * new_grid_x + x] = tiles[y * grid_x + x];
|
||||
}
|
||||
}
|
||||
|
||||
tiles = std::move(new_tiles);
|
||||
grid_x = new_grid_x;
|
||||
grid_y = new_grid_y;
|
||||
}
|
||||
|
||||
void TileLayer::render(sf::RenderTarget& target,
|
||||
float left_spritepixels, float top_spritepixels,
|
||||
int left_edge, int top_edge, int x_limit, int y_limit,
|
||||
float zoom, int cell_width, int cell_height) {
|
||||
if (!visible || !texture) return;
|
||||
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); x < x_limit; ++x) {
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); y < y_limit; ++y) {
|
||||
if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) continue;
|
||||
|
||||
int tile_index = at(x, y);
|
||||
if (tile_index < 0) continue; // No tile
|
||||
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x * cell_width - left_spritepixels) * zoom,
|
||||
(y * cell_height - top_spritepixels) * zoom
|
||||
);
|
||||
|
||||
sf::Sprite sprite = texture->sprite(tile_index, pixel_pos, sf::Vector2f(zoom, zoom));
|
||||
target.draw(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Python API - ColorLayer
|
||||
// =============================================================================
|
||||
|
||||
PyMethodDef PyGridLayerAPI::ColorLayer_methods[] = {
|
||||
{"at", (PyCFunction)PyGridLayerAPI::ColorLayer_at, METH_VARARGS,
|
||||
"at(x, y) -> Color\n\nGet the color at cell position (x, y)."},
|
||||
{"set", (PyCFunction)PyGridLayerAPI::ColorLayer_set, METH_VARARGS,
|
||||
"set(x, y, color)\n\nSet the color at cell position (x, y)."},
|
||||
{"fill", (PyCFunction)PyGridLayerAPI::ColorLayer_fill, METH_VARARGS,
|
||||
"fill(color)\n\nFill the entire layer with the specified color."},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
PyGetSetDef PyGridLayerAPI::ColorLayer_getsetters[] = {
|
||||
{"z_index", (getter)PyGridLayerAPI::ColorLayer_get_z_index,
|
||||
(setter)PyGridLayerAPI::ColorLayer_set_z_index,
|
||||
"Layer z-order. Negative values render below entities.", NULL},
|
||||
{"visible", (getter)PyGridLayerAPI::ColorLayer_get_visible,
|
||||
(setter)PyGridLayerAPI::ColorLayer_set_visible,
|
||||
"Whether the layer is rendered.", NULL},
|
||||
{"grid_size", (getter)PyGridLayerAPI::ColorLayer_get_grid_size, NULL,
|
||||
"Layer dimensions as (width, height) tuple.", NULL},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int PyGridLayerAPI::ColorLayer_init(PyColorLayerObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* kwlist[] = {"z_index", "grid_size", NULL};
|
||||
int z_index = -1;
|
||||
PyObject* grid_size_obj = nullptr;
|
||||
int grid_x = 0, grid_y = 0;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iO", const_cast<char**>(kwlist),
|
||||
&z_index, &grid_size_obj)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse grid_size if provided
|
||||
if (grid_size_obj && grid_size_obj != Py_None) {
|
||||
if (!PyTuple_Check(grid_size_obj) || PyTuple_Size(grid_size_obj) != 2) {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size must be a (width, height) tuple");
|
||||
return -1;
|
||||
}
|
||||
grid_x = PyLong_AsLong(PyTuple_GetItem(grid_size_obj, 0));
|
||||
grid_y = PyLong_AsLong(PyTuple_GetItem(grid_size_obj, 1));
|
||||
if (PyErr_Occurred()) return -1;
|
||||
}
|
||||
|
||||
// Create the layer (will be attached to grid via add_layer)
|
||||
self->data = std::make_shared<ColorLayer>(z_index, grid_x, grid_y, nullptr);
|
||||
self->grid.reset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_at(PyColorLayerObject* self, PyObject* args) {
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= self->data->grid_x || y < 0 || y >= self->data->grid_y) {
|
||||
PyErr_SetString(PyExc_IndexError, "Cell coordinates out of bounds");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const sf::Color& color = self->data->at(x, y);
|
||||
|
||||
// Return as mcrfpy.Color
|
||||
auto* color_type = (PyTypeObject*)PyObject_GetAttrString(
|
||||
PyImport_ImportModule("mcrfpy"), "Color");
|
||||
if (!color_type) return NULL;
|
||||
|
||||
PyColorObject* color_obj = (PyColorObject*)color_type->tp_alloc(color_type, 0);
|
||||
Py_DECREF(color_type);
|
||||
if (!color_obj) return NULL;
|
||||
|
||||
color_obj->data = color;
|
||||
return (PyObject*)color_obj;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_set(PyColorLayerObject* self, PyObject* args) {
|
||||
int x, y;
|
||||
PyObject* color_obj;
|
||||
if (!PyArg_ParseTuple(args, "iiO", &x, &y, &color_obj)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= self->data->grid_x || y < 0 || y >= self->data->grid_y) {
|
||||
PyErr_SetString(PyExc_IndexError, "Cell coordinates out of bounds");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Parse color
|
||||
sf::Color color;
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return NULL;
|
||||
|
||||
auto* color_type = PyObject_GetAttrString(mcrfpy_module, "Color");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!color_type) return NULL;
|
||||
|
||||
if (PyObject_IsInstance(color_obj, color_type)) {
|
||||
color = ((PyColorObject*)color_obj)->data;
|
||||
} else if (PyTuple_Check(color_obj)) {
|
||||
int r, g, b, a = 255;
|
||||
if (!PyArg_ParseTuple(color_obj, "iii|i", &r, &g, &b, &a)) {
|
||||
Py_DECREF(color_type);
|
||||
return NULL;
|
||||
}
|
||||
color = sf::Color(r, g, b, a);
|
||||
} else {
|
||||
Py_DECREF(color_type);
|
||||
PyErr_SetString(PyExc_TypeError, "color must be a Color object or (r, g, b[, a]) tuple");
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(color_type);
|
||||
|
||||
self->data->at(x, y) = color;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_fill(PyColorLayerObject* self, PyObject* args) {
|
||||
PyObject* color_obj;
|
||||
if (!PyArg_ParseTuple(args, "O", &color_obj)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Parse color
|
||||
sf::Color color;
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return NULL;
|
||||
|
||||
auto* color_type = PyObject_GetAttrString(mcrfpy_module, "Color");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!color_type) return NULL;
|
||||
|
||||
if (PyObject_IsInstance(color_obj, color_type)) {
|
||||
color = ((PyColorObject*)color_obj)->data;
|
||||
} else if (PyTuple_Check(color_obj)) {
|
||||
int r, g, b, a = 255;
|
||||
if (!PyArg_ParseTuple(color_obj, "iii|i", &r, &g, &b, &a)) {
|
||||
Py_DECREF(color_type);
|
||||
return NULL;
|
||||
}
|
||||
color = sf::Color(r, g, b, a);
|
||||
} else {
|
||||
Py_DECREF(color_type);
|
||||
PyErr_SetString(PyExc_TypeError, "color must be a Color object or (r, g, b[, a]) tuple");
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(color_type);
|
||||
|
||||
self->data->fill(color);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_get_z_index(PyColorLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return PyLong_FromLong(self->data->z_index);
|
||||
}
|
||||
|
||||
int PyGridLayerAPI::ColorLayer_set_z_index(PyColorLayerObject* self, PyObject* value, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return -1;
|
||||
}
|
||||
long z = PyLong_AsLong(value);
|
||||
if (PyErr_Occurred()) return -1;
|
||||
self->data->z_index = z;
|
||||
// TODO: Trigger re-sort in parent grid
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_get_visible(PyColorLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return PyBool_FromLong(self->data->visible);
|
||||
}
|
||||
|
||||
int PyGridLayerAPI::ColorLayer_set_visible(PyColorLayerObject* self, PyObject* value, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return -1;
|
||||
}
|
||||
int v = PyObject_IsTrue(value);
|
||||
if (v < 0) return -1;
|
||||
self->data->visible = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_get_grid_size(PyColorLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::ColorLayer_repr(PyColorLayerObject* self) {
|
||||
std::ostringstream ss;
|
||||
if (!self->data) {
|
||||
ss << "<ColorLayer (invalid)>";
|
||||
} else {
|
||||
ss << "<ColorLayer z_index=" << self->data->z_index
|
||||
<< " size=(" << self->data->grid_x << "x" << self->data->grid_y << ")"
|
||||
<< " visible=" << (self->data->visible ? "True" : "False") << ">";
|
||||
}
|
||||
return PyUnicode_FromString(ss.str().c_str());
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Python API - TileLayer
|
||||
// =============================================================================
|
||||
|
||||
PyMethodDef PyGridLayerAPI::TileLayer_methods[] = {
|
||||
{"at", (PyCFunction)PyGridLayerAPI::TileLayer_at, METH_VARARGS,
|
||||
"at(x, y) -> int\n\nGet the tile index at cell position (x, y). Returns -1 if no tile."},
|
||||
{"set", (PyCFunction)PyGridLayerAPI::TileLayer_set, METH_VARARGS,
|
||||
"set(x, y, index)\n\nSet the tile index at cell position (x, y). Use -1 for no tile."},
|
||||
{"fill", (PyCFunction)PyGridLayerAPI::TileLayer_fill, METH_VARARGS,
|
||||
"fill(index)\n\nFill the entire layer with the specified tile index."},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
PyGetSetDef PyGridLayerAPI::TileLayer_getsetters[] = {
|
||||
{"z_index", (getter)PyGridLayerAPI::TileLayer_get_z_index,
|
||||
(setter)PyGridLayerAPI::TileLayer_set_z_index,
|
||||
"Layer z-order. Negative values render below entities.", NULL},
|
||||
{"visible", (getter)PyGridLayerAPI::TileLayer_get_visible,
|
||||
(setter)PyGridLayerAPI::TileLayer_set_visible,
|
||||
"Whether the layer is rendered.", NULL},
|
||||
{"texture", (getter)PyGridLayerAPI::TileLayer_get_texture,
|
||||
(setter)PyGridLayerAPI::TileLayer_set_texture,
|
||||
"Texture atlas for tile sprites.", NULL},
|
||||
{"grid_size", (getter)PyGridLayerAPI::TileLayer_get_grid_size, NULL,
|
||||
"Layer dimensions as (width, height) tuple.", NULL},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int PyGridLayerAPI::TileLayer_init(PyTileLayerObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* kwlist[] = {"z_index", "texture", "grid_size", NULL};
|
||||
int z_index = -1;
|
||||
PyObject* texture_obj = nullptr;
|
||||
PyObject* grid_size_obj = nullptr;
|
||||
int grid_x = 0, grid_y = 0;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iOO", const_cast<char**>(kwlist),
|
||||
&z_index, &texture_obj, &grid_size_obj)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse texture
|
||||
std::shared_ptr<PyTexture> texture;
|
||||
if (texture_obj && texture_obj != Py_None) {
|
||||
// Check if it's a PyTexture
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return -1;
|
||||
|
||||
auto* texture_type = PyObject_GetAttrString(mcrfpy_module, "Texture");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!texture_type) return -1;
|
||||
|
||||
if (PyObject_IsInstance(texture_obj, texture_type)) {
|
||||
texture = ((PyTextureObject*)texture_obj)->data;
|
||||
} else {
|
||||
Py_DECREF(texture_type);
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a Texture object");
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(texture_type);
|
||||
}
|
||||
|
||||
// Parse grid_size if provided
|
||||
if (grid_size_obj && grid_size_obj != Py_None) {
|
||||
if (!PyTuple_Check(grid_size_obj) || PyTuple_Size(grid_size_obj) != 2) {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size must be a (width, height) tuple");
|
||||
return -1;
|
||||
}
|
||||
grid_x = PyLong_AsLong(PyTuple_GetItem(grid_size_obj, 0));
|
||||
grid_y = PyLong_AsLong(PyTuple_GetItem(grid_size_obj, 1));
|
||||
if (PyErr_Occurred()) return -1;
|
||||
}
|
||||
|
||||
// Create the layer
|
||||
self->data = std::make_shared<TileLayer>(z_index, grid_x, grid_y, nullptr, texture);
|
||||
self->grid.reset();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_at(PyTileLayerObject* self, PyObject* args) {
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= self->data->grid_x || y < 0 || y >= self->data->grid_y) {
|
||||
PyErr_SetString(PyExc_IndexError, "Cell coordinates out of bounds");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return PyLong_FromLong(self->data->at(x, y));
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_set(PyTileLayerObject* self, PyObject* args) {
|
||||
int x, y, index;
|
||||
if (!PyArg_ParseTuple(args, "iii", &x, &y, &index)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= self->data->grid_x || y < 0 || y >= self->data->grid_y) {
|
||||
PyErr_SetString(PyExc_IndexError, "Cell coordinates out of bounds");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->at(x, y) = index;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_fill(PyTileLayerObject* self, PyObject* args) {
|
||||
int index;
|
||||
if (!PyArg_ParseTuple(args, "i", &index)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->fill(index);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_get_z_index(PyTileLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return PyLong_FromLong(self->data->z_index);
|
||||
}
|
||||
|
||||
int PyGridLayerAPI::TileLayer_set_z_index(PyTileLayerObject* self, PyObject* value, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return -1;
|
||||
}
|
||||
long z = PyLong_AsLong(value);
|
||||
if (PyErr_Occurred()) return -1;
|
||||
self->data->z_index = z;
|
||||
// TODO: Trigger re-sort in parent grid
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_get_visible(PyTileLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return PyBool_FromLong(self->data->visible);
|
||||
}
|
||||
|
||||
int PyGridLayerAPI::TileLayer_set_visible(PyTileLayerObject* self, PyObject* value, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return -1;
|
||||
}
|
||||
int v = PyObject_IsTrue(value);
|
||||
if (v < 0) return -1;
|
||||
self->data->visible = v;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_get_texture(PyTileLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->data->texture) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
auto* texture_type = (PyTypeObject*)PyObject_GetAttrString(
|
||||
PyImport_ImportModule("mcrfpy"), "Texture");
|
||||
if (!texture_type) return NULL;
|
||||
|
||||
PyTextureObject* tex_obj = (PyTextureObject*)texture_type->tp_alloc(texture_type, 0);
|
||||
Py_DECREF(texture_type);
|
||||
if (!tex_obj) return NULL;
|
||||
|
||||
tex_obj->data = self->data->texture;
|
||||
return (PyObject*)tex_obj;
|
||||
}
|
||||
|
||||
int PyGridLayerAPI::TileLayer_set_texture(PyTileLayerObject* self, PyObject* value, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (value == Py_None) {
|
||||
self->data->texture.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto* mcrfpy_module = PyImport_ImportModule("mcrfpy");
|
||||
if (!mcrfpy_module) return -1;
|
||||
|
||||
auto* texture_type = PyObject_GetAttrString(mcrfpy_module, "Texture");
|
||||
Py_DECREF(mcrfpy_module);
|
||||
if (!texture_type) return -1;
|
||||
|
||||
if (!PyObject_IsInstance(value, texture_type)) {
|
||||
Py_DECREF(texture_type);
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a Texture object or None");
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(texture_type);
|
||||
|
||||
self->data->texture = ((PyTextureObject*)value)->data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_get_grid_size(PyTileLayerObject* self, void* closure) {
|
||||
if (!self->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Layer has no data");
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
PyObject* PyGridLayerAPI::TileLayer_repr(PyTileLayerObject* self) {
|
||||
std::ostringstream ss;
|
||||
if (!self->data) {
|
||||
ss << "<TileLayer (invalid)>";
|
||||
} else {
|
||||
ss << "<TileLayer z_index=" << self->data->z_index
|
||||
<< " size=(" << self->data->grid_x << "x" << self->data->grid_y << ")"
|
||||
<< " visible=" << (self->data->visible ? "True" : "False")
|
||||
<< " texture=" << (self->data->texture ? "set" : "None") << ">";
|
||||
}
|
||||
return PyUnicode_FromString(ss.str().c_str());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue