From 9dd6adc04d4a7477ccec17faff1645834fa03fff Mon Sep 17 00:00:00 2001 From: elenaran Date: Fri, 17 Jan 2025 19:02:08 -0600 Subject: [PATCH] Added stronghold functions and also some unit testing --- src/objects/finder.c | 156 ++++++++++++++++++++++++++++++++++++++++ src/objects/generator.c | 4 ++ tests/test_finder.py | 90 +++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 tests/test_finder.py diff --git a/src/objects/finder.c b/src/objects/finder.c index aed828b..c0e5d3f 100644 --- a/src/objects/finder.c +++ b/src/objects/finder.c @@ -70,6 +70,159 @@ static PyObject *Finder_get_structure_config(FinderObject *self, PyObject *args) } +static PyObject *Finder_is_stronghold_biome(FinderObject *self, PyObject *args) { + int id; + + if (!PyArg_ParseTuple(args, "i", &id)) { + return NULL; + } + + return PyBool_FromLong(isStrongholdBiome(self->version, id)); + +} + +static PyObject *Finder_init_first_stronghold(FinderObject *self, PyObject *args) { + uint64_t s48; + + if (!PyArg_ParseTuple(args, "K", &s48)) { + return NULL; + } + StrongholdIter sh; + initFirstStronghold(&sh, self->version, s48); + + PosObject *pos = Pos_new(&PosType, NULL, NULL); + pos->pos.x = sh.pos.x; + pos->pos.z = sh.pos.z; + + PosObject *nextapprox = Pos_new(&PosType, NULL, NULL); + + nextapprox->pos.x = sh.nextapprox.x; + nextapprox->pos.z = sh.nextapprox.z; + + + PyObject *dict = PyDict_New(); + + // Add struct fields to dictionary + PyDict_SetItemString(dict, "pos", (PyObject *)pos); + PyDict_SetItemString(dict, "nextapprox", (PyObject *)nextapprox); + PyDict_SetItemString(dict, "index", PyLong_FromLong(sh.index)); + PyDict_SetItemString(dict, "ringnum", PyLong_FromLong(sh.ringnum)); + PyDict_SetItemString(dict, "ringmax", PyLong_FromLong(sh.ringmax)); + PyDict_SetItemString(dict, "ringidx", PyLong_FromLong(sh.ringidx)); + PyDict_SetItemString(dict, "angle", PyFloat_FromDouble(sh.angle)); + PyDict_SetItemString(dict, "dist", PyFloat_FromDouble(sh.dist)); + PyDict_SetItemString(dict, "rnds", PyLong_FromLongLong(sh.rnds)); + + + PyObject *py_tuple = PyTuple_New(2); + PyTuple_SetItem(py_tuple, 0, nextapprox); // Steals a reference to PosObject + PyTuple_SetItem(py_tuple, 1, dict); // Steals a reference to py_dict + + return py_tuple; +} + + +static PyObject *Finder_next_stronghold(FinderObject *self, PyObject *args) { + PyObject *dict_obj; + PyObject *gen_obj; + + // Parse the arguments as a PyObject (dictionary) and GENERATOR_OBJECT_TYPE + if (!PyArg_ParseTuple(args, "O" GENERATOR_OBJECT_TYPE, &dict_obj, &GeneratorType, &gen_obj)) { + return NULL; + } + + // Check if the first argument is a dictionary + if (!PyDict_Check(dict_obj)) { + PyErr_SetString(PyExc_TypeError, "First parameter must be a dictionary"); + return NULL; + } + + // Check if the second argument is a GeneratorObject + if (!PyObject_TypeCheck(gen_obj, &GeneratorType)) { + PyErr_SetString(PyExc_TypeError, "Second parameter must be a GeneratorObject"); + return NULL; + } + + // Extract the 'pos' value from the dictionary + PyObject *pos_obj = PyDict_GetItemString(dict_obj, "pos"); + if (!pos_obj || !PyObject_TypeCheck(pos_obj, &PosType)) { + PyErr_SetString(PyExc_TypeError, "Dictionary must contain 'pos' key with a PosObject"); + return NULL; + } + + // Extract other values from the dictionary + PyObject *nextapprox_obj = PyDict_GetItemString(dict_obj, "nextapprox"); + if (!nextapprox_obj || !PyObject_TypeCheck(nextapprox_obj, &PosType)) { + PyErr_SetString(PyExc_TypeError, "Dictionary must contain 'nextapprox' key with a PosObject"); + return NULL; + } + + long index = PyLong_AsLong(PyDict_GetItemString(dict_obj, "index")); + long ringnum = PyLong_AsLong(PyDict_GetItemString(dict_obj, "ringnum")); + long ringmax = PyLong_AsLong(PyDict_GetItemString(dict_obj, "ringmax")); + long ringidx = PyLong_AsLong(PyDict_GetItemString(dict_obj, "ringidx")); + double angle = PyFloat_AsDouble(PyDict_GetItemString(dict_obj, "angle")); + double dist = PyFloat_AsDouble(PyDict_GetItemString(dict_obj, "dist")); + unsigned long long rnds = PyLong_AsUnsignedLongLong(PyDict_GetItemString(dict_obj, "rnds")); + + // Access the C 'Pos' structures + PosObject *pos = (PosObject *)pos_obj; + PosObject *nextapprox = (PosObject *)nextapprox_obj; + + + StrongholdIter sh; + sh.pos = pos->pos; + sh.nextapprox = nextapprox->pos; + sh.index = index; + sh.ringnum = ringnum; + sh.ringmax = ringmax; + sh.ringidx = ringidx; + sh.angle = angle; + sh.dist = dist; + sh.rnds = rnds; + sh.mc = self->version; + + GeneratorObject *generator_obj = (GeneratorObject *)gen_obj; + Generator g = generator_obj->generator; + + + int valid = nextStronghold(&sh, &g); + + PosObject *posOut = Pos_new(&PosType, NULL, NULL); + posOut->pos.x = sh.pos.x; + posOut->pos.z = sh.pos.z; + + PosObject *nextapproxOut = Pos_new(&PosType, NULL, NULL); + + nextapproxOut->pos.x = sh.nextapprox.x; + nextapproxOut->pos.z = sh.nextapprox.z; + + PyObject *dict = PyDict_New(); + + // Add struct fields to dictionary + PyDict_SetItemString(dict, "pos", (PyObject *)posOut); + PyDict_SetItemString(dict, "nextapprox", (PyObject *)nextapproxOut); + PyDict_SetItemString(dict, "index", PyLong_FromLong(sh.index)); + PyDict_SetItemString(dict, "ringnum", PyLong_FromLong(sh.ringnum)); + PyDict_SetItemString(dict, "ringmax", PyLong_FromLong(sh.ringmax)); + PyDict_SetItemString(dict, "ringidx", PyLong_FromLong(sh.ringidx)); + PyDict_SetItemString(dict, "angle", PyFloat_FromDouble(sh.angle)); + PyDict_SetItemString(dict, "dist", PyFloat_FromDouble(sh.dist)); + PyDict_SetItemString(dict, "rnds", PyLong_FromLongLong(sh.rnds)); + + PyObject *py_bool = PyBool_FromLong((long)valid); + if (!py_bool) { + return NULL; // Return NULL to indicate an error + } + + PyObject *py_tuple = PyTuple_New(2); + PyTuple_SetItem(py_tuple, 0, py_bool); // Steals a reference to py_bool + PyTuple_SetItem(py_tuple, 1, dict); // Steals a reference to py_dict + + return py_tuple; +} + + static PyObject *Finder_chunk_generate_rnd(FinderObject *self, PyObject *args) { uint64_t seed; int chunkX; @@ -111,6 +264,9 @@ static PyMemberDef Finder_members[] = { static PyMethodDef Finder_methods[] = { {"get_structure_config", (PyCFunction)Finder_get_structure_config, METH_VARARGS, "Finds a structure's configuration parameters"}, + {"is_stronghold_biome", (PyCFunction)Finder_is_stronghold_biome, METH_VARARGS, "Checks if the biome is valid for stronghold placement"}, + {"init_first_stronghold", (PyCFunction)Finder_init_first_stronghold, METH_VARARGS, "Initialises first stronghold"}, + {"next_stronghold", (PyCFunction)Finder_next_stronghold, METH_VARARGS, "Finds next stronghold"}, {"chunk_generate_rnd", (PyCFunction)Finder_chunk_generate_rnd, METH_VARARGS, "Initialises and returns a random seed used in the chunk generation"}, {"get_structure_pos", (PyCFunction)Finder_get_structure_pos, METH_VARARGS, "Finds a structures position within the given region"}, {NULL} /* Sentinel */ diff --git a/src/objects/generator.c b/src/objects/generator.c index 50d7080..4f6cf50 100644 --- a/src/objects/generator.c +++ b/src/objects/generator.c @@ -12,6 +12,10 @@ typedef struct { Generator generator; } GeneratorObject; +extern PyTypeObject GeneratorType; + +#define GENERATOR_OBJECT_TYPE "O!" + static int Generator_traverse(GeneratorObject *self, visitproc visit, void *arg) { return 0; } diff --git a/tests/test_finder.py b/tests/test_finder.py new file mode 100644 index 0000000..1bf6131 --- /dev/null +++ b/tests/test_finder.py @@ -0,0 +1,90 @@ +import pytest +from pybiomes import Finder, Generator, Pos +from pybiomes.biomes import plains +from pybiomes.structures import Village +from pybiomes.versions import MC_1_21_WD + +@pytest.fixture +def finder(): + return Finder(version=MC_1_21_WD) + +def test_get_structure_config(finder): + result = finder.get_structure_config(Village) + assert isinstance(result, dict) + assert 'salt' in result + assert result['salt'] == 10387312 + assert 'regionSize' in result + assert result['regionSize'] == 34 + assert 'chunkRange' in result + assert result['chunkRange'] == 26 + assert 'structType' in result + assert result['structType'] == Village + assert 'dim' in result + assert result['dim'] == 0 + assert 'rarity' in result + assert result['rarity'] == 0 + + +def test_is_stronghold_biome(finder): + result = finder.is_stronghold_biome(plains) + assert isinstance(result, bool) + +def test_init_first_stronghold(finder): + seed = 1234567890 + nextapprox, sh = finder.init_first_stronghold(seed) + assert isinstance(nextapprox, Pos) + assert nextapprox.x == -520 and nextapprox.z == -2600 + assert isinstance(sh, dict) + assert sh['pos'].x == 0 and sh['pos'].z == 0 + assert sh['nextapprox'].x == -520 and sh['nextapprox'].z == -2600 + assert sh['index'] == 0 + assert sh['ringnum'] == 0 + assert sh['ringmax'] == 3 + assert sh['ringidx'] == 0 + assert sh['angle'] == 4.5127238872158175 + assert sh['dist'] == 166.02303278628128 + assert sh['rnds'] == 197462054985395 + +def test_next_stronghold(finder): + sh = { + 'pos': Pos(x=0, z=0), + 'nextapprox': Pos(x=-520, z=-2600), + 'index': 0, + 'ringnum': 0, + 'ringmax': 3, + 'ringidx': 0, + 'angle': 4.5127238872158175, + 'dist': 166.02303278628128, + 'rnds': 197462054985395 + } + generator = Generator(MC_1_21_WD, 0) + is_valid, sh = finder.next_stronghold(sh, generator) + assert isinstance(is_valid, bool) + assert is_valid + assert isinstance(sh, dict) + assert sh['pos'].x == -524 and sh['pos'].z == -2604 + assert sh['nextapprox'].x == 1528 and sh['nextapprox'].z == 520 + assert sh['index'] == 1 + assert sh['ringnum'] == 0 + assert sh['ringmax'] == 3 + assert sh['ringidx'] == 1 + assert sh['angle'] == 6.607118989609013 + assert sh['dist'] == 99.97235323528389 + assert sh['rnds'] == 228792104649703 + +def test_chunk_generate_rnd(finder): + seed = 1234567890 + chunkX = 0 + chunkZ = 0 + rnd = finder.chunk_generate_rnd(seed, chunkX, chunkZ) + assert isinstance(rnd, int) + assert rnd == 24016250047 + +def test_get_structure_pos(finder): + structure = Village + seed = 1234567890 + reg_x = 0 + reg_z = 0 + pos = finder.get_structure_pos(structure, seed, reg_x, reg_z) + assert pos is None or isinstance(pos, Pos) + assert pos.x == 256 and pos.z == 64