diff --git a/assets/chunks/-1_0_0.dcf b/assets/chunks/-1_0_0.dcf index 640ff6e5..70bb6220 100644 Binary files a/assets/chunks/-1_0_0.dcf and b/assets/chunks/-1_0_0.dcf differ diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf index 74c1f785..eccdb759 100644 Binary files a/assets/chunks/0_0_0.dcf and b/assets/chunks/0_0_0.dcf differ diff --git a/assets/chunks/0_1_0.dcf b/assets/chunks/0_1_0.dcf index 7429ed6c..5dddccd4 100644 Binary files a/assets/chunks/0_1_0.dcf and b/assets/chunks/0_1_0.dcf differ diff --git a/assets/chunks/1_0_0.dcf b/assets/chunks/1_0_0.dcf index 44ace2cb..f9f7654c 100644 Binary files a/assets/chunks/1_0_0.dcf and b/assets/chunks/1_0_0.dcf differ diff --git a/assets/chunks/1_1_0.dcf b/assets/chunks/1_1_0.dcf index fb28d758..ef39549c 100644 Binary files a/assets/chunks/1_1_0.dcf and b/assets/chunks/1_1_0.dcf differ diff --git a/assets/chunks/2_0_0.dcf b/assets/chunks/2_0_0.dcf index 6d3eb0e2..d26e7bb5 100644 Binary files a/assets/chunks/2_0_0.dcf and b/assets/chunks/2_0_0.dcf differ diff --git a/assets/meshes/chunks/chunk_0_0_0_0.dmf b/assets/meshes/chunks/chunk_0_0_0_0.dmf index 3d4a13bb..8e70f8fd 100644 Binary files a/assets/meshes/chunks/chunk_0_0_0_0.dmf and b/assets/meshes/chunks/chunk_0_0_0_0.dmf differ diff --git a/assets/meshes/chunks/chunk_2_0_0_0.dmf b/assets/meshes/chunks/chunk_2_0_0_0.dmf index 119620ea..7d390ce7 100644 Binary files a/assets/meshes/chunks/chunk_2_0_0_0.dmf and b/assets/meshes/chunks/chunk_2_0_0_0.dmf differ diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h index 7f17de3e..d7936b04 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ b/src/dusk/asset/loader/chunk/assetchunkloader.h @@ -9,7 +9,7 @@ #include "asset/assetfile.h" #include "rpg/overworld/chunk.h" -#define ASSET_CHUNK_FILE_VERSION 3 +#define ASSET_CHUNK_FILE_VERSION 4 typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; diff --git a/src/dusk/rpg/entity/anim/entityanim.c b/src/dusk/rpg/entity/anim/entityanim.c index 114fc526..fa98628f 100644 --- a/src/dusk/rpg/entity/anim/entityanim.c +++ b/src/dusk/rpg/entity/anim/entityanim.c @@ -19,7 +19,7 @@ const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT] = { }; float_t entityAnimTileZOffset(const worldpos_t pos) { - return tileIsRamp(mapGetTile(pos)) ? 0.5f : 0.0f; + return tileShapeIsRamp(mapGetTile(pos).shape) ? 0.5f : 0.0f; } void entityAnimUpdate(entity_t *entity) { @@ -32,7 +32,7 @@ void entityAnimUpdate(entity_t *entity) { ) { entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN; } - entity->animation = ENTITY_ANIM_IDLE; + entity->animation = ENTITY_ANIM_IDLE; entity->animTime = 0; } } diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index 103bae0b..d7a35abd 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -95,38 +95,38 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { // Are we walking up a ramp? if( - tileIsRamp(tileCurrent) && + tileShapeIsRamp(tileCurrent.shape) && ( // Can only walk UP the direction the ramp faces. - (direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent || + (direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent.shape || // If diagonal ramp, can go up one of two ways only. Inner ramps // share the same allowed directions as their outer counterparts. ( ( ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST || - tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST_INNER + tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST || + tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) || ( ( - tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST || - tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST_INNER + tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST || + tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( ( - tileCurrent == TILE_SHAPE_RAMP_NORTHEAST || - tileCurrent == TILE_SHAPE_RAMP_NORTHEAST_INNER + tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST || + tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( ( - tileCurrent == TILE_SHAPE_RAMP_NORTHWEST || - tileCurrent == TILE_SHAPE_RAMP_NORTHWEST_INNER + tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST || + tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) @@ -135,55 +135,60 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { ) ) { tile_t tileNewSaved = tileNew; - tileNew = TILE_SHAPE_NULL; + tileNew = TILE_NULL; worldpos_t abovePos = newPos; abovePos.z += 1; tile_t tileAbove = mapGetTile(abovePos); - if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) { + if( + tileAbove.shape != TILE_SHAPE_NULL && + tileShapeIsWalkable(tileAbove.shape) + ) { raise = true; } else { tileNew = tileNewSaved; } - } else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) { + } else if(tileNew.shape == TILE_SHAPE_NULL && newPos.z > 0) { // Falling down? worldpos_t belowPos = newPos; belowPos.z -= 1; tile_t tileBelow = mapGetTile(belowPos); if( - tileBelow != TILE_SHAPE_NULL && - tileIsRamp(tileBelow) && + tileBelow.shape != TILE_SHAPE_NULL && + tileShapeIsRamp(tileBelow.shape) && ( // This handles regular cardinal ramps - (entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH) == tileBelow || + ( + entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH + ) == tileBelow.shape || // This handles diagonal ramps. Inner ramps share the same // allowed directions as their outer counterparts. ( ( ( - tileBelow == TILE_SHAPE_RAMP_SOUTHEAST || - tileBelow == TILE_SHAPE_RAMP_SOUTHEAST_INNER + tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST || + tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) ) || ( ( - tileBelow == TILE_SHAPE_RAMP_SOUTHWEST || - tileBelow == TILE_SHAPE_RAMP_SOUTHWEST_INNER + tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST || + tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER ) && (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) ) || ( ( - tileBelow == TILE_SHAPE_RAMP_NORTHEAST || - tileBelow == TILE_SHAPE_RAMP_NORTHEAST_INNER + tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST || + tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) ) || ( ( - tileBelow == TILE_SHAPE_RAMP_NORTHWEST || - tileBelow == TILE_SHAPE_RAMP_NORTHWEST_INNER + tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST || + tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER ) && (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) ) @@ -196,7 +201,7 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { } // Can we walk here? - if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked + if(!raise && !fall && !tileShapeIsWalkable(tileNew.shape)) return;// Blocked // Raise/fall must be applied before checking for blocking entities, // otherwise the check compares against the wrong z-level. diff --git a/src/dusk/rpg/overworld/CMakeLists.txt b/src/dusk/rpg/overworld/CMakeLists.txt index d5d69e56..3f1c93c5 100644 --- a/src/dusk/rpg/overworld/CMakeLists.txt +++ b/src/dusk/rpg/overworld/CMakeLists.txt @@ -10,5 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} map.c worldpos.c tile.c + tileshape.c ) diff --git a/src/dusk/rpg/overworld/chunk.c b/src/dusk/rpg/overworld/chunk.c index 19a9999e..18a5316e 100644 --- a/src/dusk/rpg/overworld/chunk.c +++ b/src/dusk/rpg/overworld/chunk.c @@ -8,11 +8,7 @@ #include "chunk.h" uint32_t chunkGetTileIndex(const chunkpos_t position) { - return ( - (position.z * CHUNK_WIDTH * CHUNK_HEIGHT) + - (position.y * CHUNK_WIDTH) + - position.x - ); + return (position.y * CHUNK_WIDTH) + position.x; } bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) { diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index ac176c91..ea803ca0 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -188,7 +188,10 @@ errorret_t mapChunkLoad(chunk_t *chunk) { ); if(!assetFileExists(name)) { - memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); + for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { + // chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND, .z = 0 }; + chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND }; + } errorOk(); } @@ -229,17 +232,19 @@ chunk_t *mapGetChunk(const uint8_t index) { } tile_t mapGetTile(const worldpos_t position) { - if(!mapIsLoaded()) return TILE_SHAPE_NULL; + if(!mapIsLoaded()) return TILE_NULL; chunkpos_t chunkPos; worldPosToChunkPos(&position, &chunkPos); chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos); - if(chunkIndex == -1) return TILE_SHAPE_NULL; + if(chunkIndex == -1) return TILE_NULL; chunk_t *chunk = mapGetChunk(chunkIndex); assertNotNull(chunk, "Chunk pointer cannot be NULL"); chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); - return chunk->tiles[tileIndex]; + tile_t tile = chunk->tiles[tileIndex]; + if(tile.z != worldPosToChunkLocalZ(&position)) return TILE_NULL; + return tile; } bool_t mapGetWalkableZNear( @@ -255,7 +260,7 @@ bool_t mapGetWalkableZNear( }; for(uint8_t i = 0; i < 3; i++) { const worldpos_t pos = { x, y, candidates[i] }; - if(!tileIsWalkable(mapGetTile(pos))) continue; + if(!tileShapeIsWalkable(mapGetTile(pos).shape)) continue; *outZ = candidates[i]; return true; } @@ -294,7 +299,7 @@ void mapChunkLoadError(void *params, void *user) { ); assetUnlockEntry(chunk->dcfEntry); chunk->dcfEntry = NULL; - memorySet(chunk->tiles, TILE_SHAPE_GROUND, sizeof(chunk->tiles)); + memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles)); } void mapChunkLoaded(void *params, void *user) { diff --git a/src/dusk/rpg/overworld/tile.c b/src/dusk/rpg/overworld/tile.c index a6f2914c..34e25cda 100644 --- a/src/dusk/rpg/overworld/tile.c +++ b/src/dusk/rpg/overworld/tile.c @@ -6,34 +6,3 @@ */ #include "tile.h" - -bool_t tileIsWalkable(const tile_t tile) { - switch(tile) { - case TILE_SHAPE_NULL: - return false; - - default: - return true; - } -} - -bool_t tileIsRamp(const tile_t tile) { - switch(tile) { - case TILE_SHAPE_RAMP_NORTH: - case TILE_SHAPE_RAMP_SOUTH: - case TILE_SHAPE_RAMP_EAST: - case TILE_SHAPE_RAMP_WEST: - case TILE_SHAPE_RAMP_NORTHEAST: - case TILE_SHAPE_RAMP_NORTHWEST: - case TILE_SHAPE_RAMP_SOUTHEAST: - case TILE_SHAPE_RAMP_SOUTHWEST: - case TILE_SHAPE_RAMP_NORTHEAST_INNER: - case TILE_SHAPE_RAMP_NORTHWEST_INNER: - case TILE_SHAPE_RAMP_SOUTHEAST_INNER: - case TILE_SHAPE_RAMP_SOUTHWEST_INNER: - return true; - - default: - return false; - } -} \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tile.h b/src/dusk/rpg/overworld/tile.h index 6abb1093..82f30850 100644 --- a/src/dusk/rpg/overworld/tile.h +++ b/src/dusk/rpg/overworld/tile.h @@ -6,41 +6,15 @@ */ #pragma once -#include "rpg/entity/entitydir.h" +#include "tileshape.h" +typedef struct { + tileshape_t shape; -typedef enum { - TILE_SHAPE_NULL = 0, - - TILE_SHAPE_GROUND = 1, - TILE_SHAPE_RAMP_NORTH = 2, - TILE_SHAPE_RAMP_EAST = 3, - TILE_SHAPE_RAMP_SOUTH = 4, - TILE_SHAPE_RAMP_WEST = 5, - TILE_SHAPE_RAMP_NORTHEAST = 6, - TILE_SHAPE_RAMP_NORTHWEST = 7, - TILE_SHAPE_RAMP_SOUTHEAST = 8, - TILE_SHAPE_RAMP_SOUTHWEST = 9, - TILE_SHAPE_RAMP_NORTHEAST_INNER = 10, - TILE_SHAPE_RAMP_NORTHWEST_INNER = 11, - TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12, - TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13, - - TILE_SHAPE_COUNT + // Local Z layer (0..CHUNK_DEPTH-1) this tile occupies within its chunk's + // depth slab. Used for entity navigation since chunk_t only stores one + // tile per X/Y column rather than one per X/Y/Z. + uint8_t z; } tile_t; -/** - * Returns whether or not the given tile is walkable. - * - * @param tile The tile to check. - * @return bool_t True if walkable, false if not. - */ -bool_t tileIsWalkable(const tile_t tile); - -/** - * Returns whether or not the given tile is a ramp tile. - * - * @param tile The tile to check. - * @return bool_t True if ramp, false if not. - */ -bool_t tileIsRamp(const tile_t tile); \ No newline at end of file +#define TILE_NULL ((tile_t){ .shape = TILE_SHAPE_NULL, .z = 0 }) \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tileshape.c b/src/dusk/rpg/overworld/tileshape.c new file mode 100644 index 00000000..13124c6f --- /dev/null +++ b/src/dusk/rpg/overworld/tileshape.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "tileshape.h" + +bool_t tileShapeIsWalkable(const tileshape_t shape) { + switch(shape) { + case TILE_SHAPE_NULL: + return false; + + default: + return true; + } +} + +bool_t tileShapeIsRamp(const tileshape_t shape) { + switch(shape) { + case TILE_SHAPE_RAMP_NORTH: + case TILE_SHAPE_RAMP_SOUTH: + case TILE_SHAPE_RAMP_EAST: + case TILE_SHAPE_RAMP_WEST: + case TILE_SHAPE_RAMP_NORTHEAST: + case TILE_SHAPE_RAMP_NORTHWEST: + case TILE_SHAPE_RAMP_SOUTHEAST: + case TILE_SHAPE_RAMP_SOUTHWEST: + case TILE_SHAPE_RAMP_NORTHEAST_INNER: + case TILE_SHAPE_RAMP_NORTHWEST_INNER: + case TILE_SHAPE_RAMP_SOUTHEAST_INNER: + case TILE_SHAPE_RAMP_SOUTHWEST_INNER: + return true; + + default: + return false; + } +} \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tileshape.h b/src/dusk/rpg/overworld/tileshape.h new file mode 100644 index 00000000..1187ac70 --- /dev/null +++ b/src/dusk/rpg/overworld/tileshape.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/entity/entitydir.h" + +typedef enum { + TILE_SHAPE_NULL = 0, + + TILE_SHAPE_GROUND = 1, + TILE_SHAPE_RAMP_NORTH = 2, + TILE_SHAPE_RAMP_EAST = 3, + TILE_SHAPE_RAMP_SOUTH = 4, + TILE_SHAPE_RAMP_WEST = 5, + TILE_SHAPE_RAMP_NORTHEAST = 6, + TILE_SHAPE_RAMP_NORTHWEST = 7, + TILE_SHAPE_RAMP_SOUTHEAST = 8, + TILE_SHAPE_RAMP_SOUTHWEST = 9, + TILE_SHAPE_RAMP_NORTHEAST_INNER = 10, + TILE_SHAPE_RAMP_NORTHWEST_INNER = 11, + TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12, + TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13, + + TILE_SHAPE_COUNT +} tileshape_t; + +/** + * Returns whether or not the given tile shape is a ramp. + * + * @param shape The tile shape to check. + * @return bool_t True if ramp, false if not. + */ +bool_t tileShapeIsRamp(const tileshape_t shape); + +/** + * Returns whether or not the given tile shape is walkable. + * + * @param shape The tile shape to check. + * @return bool_t True if walkable, false if not. + */ +bool_t tileShapeIsWalkable(const tileshape_t shape); \ No newline at end of file diff --git a/src/dusk/rpg/overworld/worldpos.c b/src/dusk/rpg/overworld/worldpos.c index 3cdd7100..57bf5118 100644 --- a/src/dusk/rpg/overworld/worldpos.c +++ b/src/dusk/rpg/overworld/worldpos.c @@ -47,7 +47,7 @@ void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) { chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) { assertNotNull(worldPos, "World position pointer cannot be NULL"); - uint8_t localX, localY, localZ; + uint8_t localX, localY; if(worldPos->x < 0) { localX = (uint8_t)( (CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH) @@ -64,18 +64,8 @@ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) { localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT); } - if(worldPos->z < 0) { - localZ = (uint8_t)( - (CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH) - ); - } else { - localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH); - } - chunktileindex_t chunkTileIndex = (chunktileindex_t)( - (localZ * CHUNK_WIDTH * CHUNK_HEIGHT) + - (localY * CHUNK_WIDTH) + - localX + (localY * CHUNK_WIDTH) + localX ); assertTrue( chunkTileIndex < CHUNK_TILE_COUNT, @@ -84,6 +74,17 @@ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) { return chunkTileIndex; } +uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos) { + assertNotNull(worldPos, "World position pointer cannot be NULL"); + + if(worldPos->z < 0) { + return (uint8_t)( + (CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH) + ); + } + return (uint8_t)(worldPos->z % CHUNK_DEPTH); +} + chunkindex_t chunkPosToIndex(const chunkpos_t* pos) { assertNotNull(pos, "Chunk position pointer cannot be NULL"); diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index cb8c7575..f05a0069 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -19,9 +19,11 @@ #define CHUNK_WIDTH 16 #define CHUNK_HEIGHT 16 -#define CHUNK_DEPTH 4 +#define CHUNK_DEPTH 8 -#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) +// Chunks store one tile per X/Y column, not one per X/Y/Z - each tile +// records its own local Z layer (see tile_t) instead. +#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT) #define MAP_CHUNK_WIDTH 3 #define MAP_CHUNK_HEIGHT 3 @@ -48,6 +50,10 @@ typedef uint32_t chunktileindex_t; typedef int32_t worldunits_t; typedef int32_t chunkunits_t; +typedef struct worldpos2d_s { + worldunit_t x, y; +} worldpos2d_t; + typedef struct worldpos_s { worldunit_t x, y, z; } worldpos_t; @@ -84,12 +90,21 @@ void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out); /** * Converts a position in world-space to an index inside a chunk that the tile * resides in. - * + * * @param worldPos The world position. * @return The tile index within the chunk. */ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos); +/** + * Converts a world-space Z coordinate to the local Z layer (0..CHUNK_DEPTH-1) + * within the chunk that owns it, for comparison against tile_t.z. + * + * @param worldPos The world position. + * @return The local Z layer within the owning chunk. + */ +uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos); + /** * Converts a chunk position to a world position. * diff --git a/tools/asset/chunk/__main__.py b/tools/asset/chunk/__main__.py index 1c207cee..60ccec06 100644 --- a/tools/asset/chunk/__main__.py +++ b/tools/asset/chunk/__main__.py @@ -5,7 +5,7 @@ """ Generates DCF + companion DMF files from raw chunk JSON files, or upgrades -legacy DCF files (version 1 or 2) to version 3. +legacy DCF files (version 1 or 2) to the current version. JSON input (assetsraw/chunks/chunk_X_Y_Z.json): { @@ -17,14 +17,21 @@ JSON input (assetsraw/chunks/chunk_X_Y_Z.json): ] } - Tiles absent from the array default to TILE_SHAPE_NULL. + Tiles absent from the array default to TILE_SHAPE_NULL. Each (x, y) + column may only have ONE real tile - chunks store one tile per column, + not one per (x, y, z), and that tile records its own local z (see the + version 4 tile format below). If two entries share the same (x, y) with + different z, the later one in the array wins and a warning is printed. Mesh files are located by searching under assets/meshes/ and referenced by path from the assets root in the DCF. Output DCF is derived automatically: assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf -Version 3 DCF format (after 8-byte header + tiles): +Version 4 DCF format (after 8-byte header): + tile_t tiles[CHUNK_WIDTH * CHUNK_HEIGHT] (one per x/y column) + each tile: uint32_t shape, uint8_t z, 3 padding bytes (8 bytes total, + matching the C tile_t struct's layout: { tileshape_t shape; uint8_t z; }) uint8_t meshCount for each model: null-terminated string (relative asset path to .json model) @@ -40,7 +47,7 @@ DMF format: Usage: python3 -m tools.asset.chunk # process all assetsraw/chunks/*.json python3 -m tools.asset.chunk # process one JSON file - python3 -m tools.asset.chunk # upgrade legacy DCF in-place + python3 -m tools.asset.chunk # upgrade legacy (v1/v2) DCF in-place """ import json @@ -56,8 +63,10 @@ ASSETS_DIR = os.path.join(PROJECT_ROOT, 'assets') CHUNK_WIDTH = 16 CHUNK_HEIGHT = 16 -CHUNK_DEPTH = 4 -CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 1024 +CHUNK_DEPTH = 8 +# One tile per (x, y) column - the column's local Z (0..CHUNK_DEPTH-1) is +# carried on the tile itself instead of storing a tile per (x, y, z). +CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT # 256 # World-space Z distance of one Z-layer/story, in world-float space. # Must match WORLD_LAYER_HEIGHT in src/dusk/rpg/overworld/worldpos.h. @@ -66,9 +75,20 @@ WORLD_LAYER_HEIGHT = 1.0 / math.sqrt(2) CHUNK_MESH_COUNT_MAX = 10 CHUNK_MESH_NAME_MAX = 64 -TILE_SIZE = 4 +# Matches sizeof(tile_t) on the C side: uint32_t shape + uint8_t z, padded +# to 8 bytes ({ tileshape_t shape; uint8_t z; } with 4-byte enum alignment). +TILE_STRUCT_FORMAT = ' {rel}') - write_v3(dcf_path, bytes(tiles), model_names, mesh_offsets) + write_dcf(dcf_path, bytes(tiles), model_names, mesh_offsets) def process_json(json_path): @@ -306,9 +334,40 @@ def process_json(json_path): # --------------------------------------------------------------------------- -# Legacy DCF (v1/v2) -> v3 +# Legacy DCF (v1/v2) -> current version # --------------------------------------------------------------------------- +def collapse_legacy_tiles(legacy_tiles, path): + """Collapse the old one-tile-per-(x,y,z) grid into the current + one-tile-per-(x,y) format. If a column has more than one non-null tile + across its Z layers, the highest Z wins and a warning is printed.""" + tiles = bytearray(CHUNK_TILE_COUNT * TILE_SIZE) + for y in range(CHUNK_HEIGHT): + for x in range(CHUNK_WIDTH): + found = [] + for z in range(_LEGACY_CHUNK_DEPTH): + legacy_idx = x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT + shape = struct.unpack_from( + ' 1: + print( + f' WARNING: {path}: column ({x}, {y}) has tiles at ' + f'z={[z for z, _ in found]} - only one tile per column ' + f'is kept, z={found[-1][0]} wins (highest Z)' + ) + z, shape = found[-1] + struct.pack_into( + TILE_STRUCT_FORMAT, tiles, tile_index(x, y) * TILE_SIZE, + shape, z + ) + return bytes(tiles) + + def read_legacy_dcf(path): with open(path, 'rb') as f: data = f.read() @@ -319,8 +378,8 @@ def read_legacy_dcf(path): raise ValueError(f"{path}: expected version 1 or 2, got {version}") offset = 8 - tiles_size = CHUNK_TILE_COUNT * TILE_SIZE - tiles = data[offset:offset + tiles_size] + tiles_size = _LEGACY_CHUNK_TILE_COUNT * _LEGACY_TILE_SIZE + tiles = collapse_legacy_tiles(data[offset:offset + tiles_size], path) offset += tiles_size meshes = [] @@ -362,7 +421,7 @@ def upgrade_dcf(path): dmf_name = f'chunk_{base}_{idx}.dmf' write_dmf(os.path.join(terrain_dir, dmf_name), verts) mesh_names.append(f'meshes/chunks/{dmf_name}') - write_v3(path, tiles, mesh_names) + write_dcf(path, tiles, mesh_names) # ---------------------------------------------------------------------------