From a292f1992b07bffb7d3c809ba43baeecef85242a Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 4 Jul 2026 08:40:53 -0500 Subject: [PATCH] Change to use the Z tile system --- assets/chunks/-1_0_0.dcf | Bin 4283 -> 2235 bytes assets/chunks/0_0_0.dcf | Bin 4194 -> 2146 bytes assets/chunks/0_1_0.dcf | Bin 4282 -> 2234 bytes assets/chunks/1_0_0.dcf | Bin 4370 -> 2322 bytes assets/chunks/1_1_0.dcf | Bin 4282 -> 2234 bytes assets/chunks/2_0_0.dcf | Bin 4150 -> 2102 bytes assets/meshes/chunks/chunk_0_0_0_0.dmf | Bin 28932 -> 28932 bytes assets/meshes/chunks/chunk_2_0_0_0.dmf | Bin 33852 -> 30252 bytes .../asset/loader/chunk/assetchunkloader.h | 2 +- src/dusk/rpg/entity/anim/entityanim.c | 4 +- src/dusk/rpg/entity/entity.c | 55 +++---- src/dusk/rpg/overworld/CMakeLists.txt | 1 + src/dusk/rpg/overworld/chunk.c | 6 +- src/dusk/rpg/overworld/map.c | 17 ++- src/dusk/rpg/overworld/tile.c | 31 ---- src/dusk/rpg/overworld/tile.h | 42 +----- src/dusk/rpg/overworld/tileshape.c | 39 +++++ src/dusk/rpg/overworld/tileshape.h | 45 ++++++ src/dusk/rpg/overworld/worldpos.c | 25 ++-- src/dusk/rpg/overworld/worldpos.h | 21 ++- tools/asset/chunk/__main__.py | 141 +++++++++++++----- 21 files changed, 269 insertions(+), 160 deletions(-) create mode 100644 src/dusk/rpg/overworld/tileshape.c create mode 100644 src/dusk/rpg/overworld/tileshape.h diff --git a/assets/chunks/-1_0_0.dcf b/assets/chunks/-1_0_0.dcf index 640ff6e51991c01f309f30a246cc2d317c476fc6..70bb62209e75de265f3e91e145eb939fa13cea15 100644 GIT binary patch literal 2235 zcmZ>9c4J^+U|?VbVh|WCG{iNyNm{!Tx6KflQTyo=4zvzudYO-&p6F#S%$|X^ALb51 zG`c$p@d=qnsyre4(B(-rpOAWV^9k_@nKvpA3Bl2Dz#k4Qx%nxnImP9Mt*5=YP?Cj30$oM14Dy7ND~gVX7R=_wIFo@ Vj(F4>#T&uYIsnxj0GiFf003?mG?f4V literal 4283 zcmZ>9c4J^>U|?XR5(XNB0!TF%MKh2y)bx|;UUJpL!fBxNW3w0LCUhE`T5MwI`e1x) zYO#sI)T7hb)Q*aw<}LIv8|9CNz-S1JhQMeDjE2By2#kinXb6mkz-R~z-wZH=loRrMG^kV&t{L9c4J^+U|?VbVh|W;8sZAVB(2;@*k*`|;c7pv{6#Hu*`N``L?X=&RZpoqn32o@ znMEvSMbZailIxC9c4J^>U|?XR0tOm^0Z1_yLo-l-p883#ix{}kQ)YC zKL^l_EJ&CK$uq>bAKib*{uv1U$o}CZBK*gUIjU|n1V%$(Gz3ONU^E0qLtr!n273rF<>sfP<`nBEXO!k;L#cQJ2-M3e o&d)>f090{OX=YAJW?p)+enx(2acaD2yfI8|gFOQS2s1DM0HhupB>(^b diff --git a/assets/chunks/0_1_0.dcf b/assets/chunks/0_1_0.dcf index 7429ed6c6b17946c4bb055e0487e945809bd2480..5dddccd4dbe4e937da5c98a8a5e7e7ef7522b38a 100644 GIT binary patch literal 2234 zcmZ>9c4J^+U|?VbVh|W;8sZA_NlM*MzReJw==S3>4_zIMk4rtV;xKbyG%kHGd2||= zdSb1w znwJfw;tk>rf!IJVt2jRoB_Ti+CzWRAq-5r$7wc!_mlmhSTg02d)Hc{NFn}-v0}i#u b@y0N<297{w4tUfW#T&uYIsnyy)iVG9`nfam literal 4282 zcmeH^K?=e!5JgAyJT|s2+*wh)#(=iiYE2d)dyj4^9gGW8s6|13V3?4;pP?_i?VhGY zG#R1%717l9c4J^+U|?VbVh|W;8sZ8}l3wn@v>76T%YJnAxYW@~e5Bh?t8k^2x#;l)SYz@=fM&(n1(7&D$UGE$;?YH*3ZZ9c4J^>U|?XR0tOlZ2jrLs*9>GJ>xYRWi;=<}X#J$Pl@zrwe}QOH^ug4k>qq93 zVruKmYP#Fm0Qfq>0E`uW;wTAJAFmnxn>J;#)HAR>k091DX>;?cyt4_lJ diff --git a/assets/chunks/1_1_0.dcf b/assets/chunks/1_1_0.dcf index fb28d7586d74486a2bf83b8b14110332ddff6940..ef39549c63a6943638f69beab62f9f7534694aa8 100644 GIT binary patch literal 2234 zcmZ>9c4J^+U|?VbVh|XmQNsbkBTUjN+z8u@>Ry;T(DlK@Nu|-v!No^67hN5?Jh6Oq zb8zv|%|lm*E>A2U-5gweV$B;>2MNK^a2TB7Fq*yxXS~olzp&)yr=;c->nCTF=4C^v zctapGh&Ry7D$dVCNf=PYNu`-NDVcfc#rhffrNyc7rtzjQwGIwI4Is>bL#i3=9AuWiz}0 literal 4282 zcmeH^K?=e!5JgAyAfCXsmaeT-yvBgG*lJA{A$yPR2>lpo6a*Kd(-MAX@&=OEO|_*d z5ly~m$x%knJLek>O9S)RLmf*nVS+;sbu7Vz2@XBfu>=!ljMLuzzkb5SKl={vuHR}v z4X6P%pa#@{8c+jjKn?u1f!W#CP4AL@>&4p6Ci=h%>tY diff --git a/assets/chunks/2_0_0.dcf b/assets/chunks/2_0_0.dcf index 6d3eb0e229e6d02adbfba5e0f79f1980fb8f11b0..d26e7bb528801010fb62e7275a921da5084cf6c8 100644 GIT binary patch literal 2102 zcmZ>9c4J^+U|?VbVh|XmN5f&LqyuJXdL@>|=PyXCBS>QM0kQVN)D33)iSaM8`$1&} z6A%+ilWIO8^{_IFSelS|q{@Tr1p#7dQq3P#4+-DVaG+B-9c4J^>U|?Vz#RD1!z~Bc1a^rzg{p8w9Yke$0k72{Kws)kN4@*Cz^k@iBGX${Z zU69x)7!85Z5E%R+Fq(e{e;kdvaPWozu687`E*@z8#JY1--OvsJ#@zgr)SP1d0kGC6v&Lo60+SLc7_;sum;{s3Xc)7uXt)BCMrahXnk}3JlcH-F zvwmy10+SYN6thY)d@|&+{TwfTOrGT^G5MUwjmc7; IKz_hA0Qa^numAu6 diff --git a/assets/meshes/chunks/chunk_2_0_0_0.dmf b/assets/meshes/chunks/chunk_2_0_0_0.dmf index 119620ea80cac9e32a558858f740c708bfd1e083..7d390ce7063abdb7dd71caa3343fe5795eea1a05 100644 GIT binary patch delta 151 zcmdnf!L;TLBbSS>8v`Q)1H%i}ja*3@o9|Q~VwxwT|-{b|_T$A~91U5f21j-83b4{+R7uY(W2M|4{m-O(@(3fYK?DFaz zYsj*!SEC=Z+Z9^(vh>xnb0-Uy)fM|B5TEqHb;f!Kg|%rK^$OJS3OQ?0@~kEB&RxgT z5TVBEX#=p{cn-ZS6>_%H+N>3*ry+vNOLI6A+C8Cl73#89pq_>(trm}VOAhUq3prnD zebx%p(-5WA;?XameOfNii)w*ROZ++3Q_)tnOwX^{(MGj1JR`H#N>(9(@fPP3s#j!F z8KH^hbZF9;+6fF?G#w9wHArjX0fR2qDvyu@cEtsj*1Lcq#vsOvrNn(&6EB^N?jiiK zw8jSvF$OW7J2uwE7xU7|m^}P2ITy+pZwxVJB{nvS2k_EKBVJd;(u}p@8cpNhjBBkZ?f)#2ecuE9K&J{3_fu+QKS`!Z#>y)wX5sFK(TtR}dD@bwc zfI$o_HrB;ZO09_pESWUor9F7o04C?kSH{3%W21NgFP#kHr9A?L(T&D99kmL1okWMN zA`cs4`4qOw^txe3JFUHu#IWH~4i+~;KApnsC0cLFVdJLJ#IhW=v2y zrpNPH`Z;e$o#lfJ{ccXt<}EwAHh++bHY>*&`c|2uh3j^7xh}5n*Qe-vU2vP7%s)9r Bo09+l 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) # ---------------------------------------------------------------------------