Change to use the Z tile system
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -10,5 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
map.c
|
||||
worldpos.c
|
||||
tile.c
|
||||
tileshape.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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
#define TILE_NULL ((tile_t){ .shape = TILE_SHAPE_NULL, .z = 0 })
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
+100
-41
@@ -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 <file.json> # process one JSON file
|
||||
python3 -m tools.asset.chunk <file.dcf> # upgrade legacy DCF in-place
|
||||
python3 -m tools.asset.chunk <file.dcf> # 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 = '<IB3x'
|
||||
TILE_SIZE = struct.calcsize(TILE_STRUCT_FORMAT)
|
||||
VERTEX_SIZE = 20
|
||||
|
||||
# Legacy (v1/v2) DCFs stored one uint32 shape per (x, y, z) triple across
|
||||
# the full 3D grid, at whatever CHUNK_DEPTH was in effect when they were
|
||||
# written (4). Frozen here, independent of the live CHUNK_DEPTH above, so
|
||||
# a future depth change can't corrupt parsing of genuinely old files.
|
||||
_LEGACY_CHUNK_DEPTH = 4
|
||||
_LEGACY_TILE_SIZE = 4
|
||||
_LEGACY_CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * _LEGACY_CHUNK_DEPTH
|
||||
|
||||
TILE_SHAPE_NULL = 0
|
||||
TILE_SHAPE_GROUND = 1
|
||||
TILE_SHAPE_RAMP_NORTH = 2
|
||||
@@ -86,7 +106,7 @@ TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13
|
||||
|
||||
FILE_MAGIC = b'DCF'
|
||||
DMF_MAGIC = b'DMF\x00'
|
||||
VERSION_OUT = 3
|
||||
VERSION_OUT = 4
|
||||
DMF_VERSION = 1
|
||||
|
||||
|
||||
@@ -94,8 +114,8 @@ DMF_VERSION = 1
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def tile_index(x, y, z):
|
||||
return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT
|
||||
def tile_index(x, y):
|
||||
return x + y * CHUNK_WIDTH
|
||||
|
||||
|
||||
def find_model(filename):
|
||||
@@ -143,8 +163,8 @@ def write_dmf(path, vertex_bytes):
|
||||
print(f' Wrote DMF {path}: {vert_count} vertices, {len(buf)} bytes')
|
||||
|
||||
|
||||
def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None):
|
||||
"""Write a v3 DCF referencing the given DMF asset paths."""
|
||||
def write_dcf(dcf_path, tiles, mesh_names, mesh_offsets=None):
|
||||
"""Write a current-version DCF referencing the given DMF asset paths."""
|
||||
mesh_count = len(mesh_names)
|
||||
if mesh_offsets is None:
|
||||
mesh_offsets = [(0.0, 0.0, 0.0)] * mesh_count
|
||||
@@ -218,29 +238,28 @@ _RAMP_CORNERS = {
|
||||
def build_terrain_verts(tiles_bytes):
|
||||
"""Generate quads for every non-null tile, shaped to match tile type."""
|
||||
buf = bytearray()
|
||||
for z in range(CHUNK_DEPTH):
|
||||
for y in range(CHUNK_HEIGHT):
|
||||
for x in range(CHUNK_WIDTH):
|
||||
idx = tile_index(x, y, z)
|
||||
tile_type = struct.unpack_from(
|
||||
'<I', tiles_bytes, idx * TILE_SIZE
|
||||
)[0]
|
||||
corners = _RAMP_CORNERS.get(tile_type)
|
||||
if corners is None:
|
||||
continue
|
||||
fx, fy, fz = float(x), float(y), float(z)
|
||||
u0 = x / CHUNK_WIDTH
|
||||
u1 = (x + 1) / CHUNK_WIDTH
|
||||
v0 = y / CHUNK_HEIGHT
|
||||
v1 = (y + 1) / CHUNK_HEIGHT
|
||||
sw, se, ne, nw = corners
|
||||
buf += _tile_quad(
|
||||
u0, u1, v0, v1, fx, fy,
|
||||
(fz + sw) * WORLD_LAYER_HEIGHT,
|
||||
(fz + se) * WORLD_LAYER_HEIGHT,
|
||||
(fz + ne) * WORLD_LAYER_HEIGHT,
|
||||
(fz + nw) * WORLD_LAYER_HEIGHT
|
||||
)
|
||||
for y in range(CHUNK_HEIGHT):
|
||||
for x in range(CHUNK_WIDTH):
|
||||
idx = tile_index(x, y)
|
||||
tile_type, z = struct.unpack_from(
|
||||
'<IB', tiles_bytes, idx * TILE_SIZE
|
||||
)
|
||||
corners = _RAMP_CORNERS.get(tile_type)
|
||||
if corners is None:
|
||||
continue
|
||||
fx, fy, fz = float(x), float(y), float(z)
|
||||
u0 = x / CHUNK_WIDTH
|
||||
u1 = (x + 1) / CHUNK_WIDTH
|
||||
v0 = y / CHUNK_HEIGHT
|
||||
v1 = (y + 1) / CHUNK_HEIGHT
|
||||
sw, se, ne, nw = corners
|
||||
buf += _tile_quad(
|
||||
u0, u1, v0, v1, fx, fy,
|
||||
(fz + sw) * WORLD_LAYER_HEIGHT,
|
||||
(fz + se) * WORLD_LAYER_HEIGHT,
|
||||
(fz + ne) * WORLD_LAYER_HEIGHT,
|
||||
(fz + nw) * WORLD_LAYER_HEIGHT
|
||||
)
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
@@ -249,11 +268,20 @@ def from_json(json_path, dcf_path):
|
||||
data = json.load(f)
|
||||
|
||||
tiles = bytearray(CHUNK_TILE_COUNT * TILE_SIZE)
|
||||
column_z = {}
|
||||
for tile in data.get('tiles', []):
|
||||
pos = tile['pos']
|
||||
x, y, z = int(pos[0]), int(pos[1]), int(pos[2])
|
||||
if (x, y) in column_z and column_z[(x, y)] != z:
|
||||
print(
|
||||
f' WARNING: {json_path}: column ({x}, {y}) has tiles at '
|
||||
f'z={column_z[(x, y)]} and z={z} - only one tile per '
|
||||
f'column is kept, z={z} wins (last one in the array)'
|
||||
)
|
||||
column_z[(x, y)] = z
|
||||
struct.pack_into(
|
||||
'<I', tiles, tile_index(x, y, z) * TILE_SIZE, int(tile['type'])
|
||||
TILE_STRUCT_FORMAT, tiles, tile_index(x, y) * TILE_SIZE,
|
||||
int(tile['type']), z
|
||||
)
|
||||
|
||||
terrain_verts = build_terrain_verts(tiles)
|
||||
@@ -295,7 +323,7 @@ def from_json(json_path, dcf_path):
|
||||
mesh_offsets.append((float(pos[0]), float(pos[1]), float(pos[2])))
|
||||
print(f' Resolved {filename} -> {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(
|
||||
'<I', legacy_tiles, legacy_idx * _LEGACY_TILE_SIZE
|
||||
)[0]
|
||||
if shape != TILE_SHAPE_NULL:
|
||||
found.append((z, shape))
|
||||
if not found:
|
||||
continue
|
||||
if len(found) > 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)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user