Change to use the Z tile system

This commit is contained in:
2026-07-04 08:40:53 -05:00
parent 88ddf429b7
commit a292f1992b
21 changed files with 269 additions and 160 deletions
@@ -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;
+2 -2
View File
@@ -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;
}
}
+30 -25
View File
@@ -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.
+1
View File
@@ -10,5 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
map.c
worldpos.c
tile.c
tileshape.c
)
+1 -5
View File
@@ -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) {
+11 -6
View File
@@ -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) {
-31
View File
@@ -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;
}
}
+8 -34
View File
@@ -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 })
+39
View File
@@ -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;
}
}
+45
View File
@@ -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);
+13 -12
View File
@@ -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");
+18 -3
View File
@@ -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.
*