diff --git a/src/rpg/CMakeLists.txt b/src/rpg/CMakeLists.txt index b3123df..4178186 100644 --- a/src/rpg/CMakeLists.txt +++ b/src/rpg/CMakeLists.txt @@ -10,4 +10,5 @@ target_sources(${DUSK_TARGET_NAME} ) # Subdirs -add_subdirectory(entity) \ No newline at end of file +add_subdirectory(entity) +add_subdirectory(world) \ No newline at end of file diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c index c210839..ab329d3 100644 --- a/src/rpg/entity/entity.c +++ b/src/rpg/entity/entity.c @@ -45,4 +45,19 @@ void entityUpdate(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); + + // Velocity + for(uint8_t i = 0; i < WORLD_DIMENSIONS; i++) { + if(entity->velocity[i] == 0) continue; + + worldChunkPosAdd(&entity->position[i], entity->velocity[i]); + + if(entity->velocity[i] < 0) { + entity->velocity[i] += ENTITY_FRICTION; + if(entity->velocity[i] >= -ENTITY_MIN_VELOCITY) entity->velocity[i] = 0; + } else { + entity->velocity[i] -= ENTITY_FRICTION; + if(entity->velocity[i] <= ENTITY_MIN_VELOCITY) entity->velocity[i] = 0; + } + } } \ No newline at end of file diff --git a/src/rpg/entity/entity.h b/src/rpg/entity/entity.h index 67092af..80a7cfe 100644 --- a/src/rpg/entity/entity.h +++ b/src/rpg/entity/entity.h @@ -29,8 +29,8 @@ typedef enum { typedef struct entity_s { entitytype_t type; direction_t direction; - worldchunkpos_t position[3]; - worldsubtile_t velocity[3]; + worldchunkpos_t position[WORLD_DIMENSIONS]; + worldsubtile_t velocity[WORLD_DIMENSIONS]; union { player_t player; diff --git a/src/rpg/rpg.c b/src/rpg/rpg.c index 285b977..6046e08 100644 --- a/src/rpg/rpg.c +++ b/src/rpg/rpg.c @@ -11,6 +11,10 @@ errorret_t rpgInit(void) { // TEST entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); + // ENTITIES[0].position[0].tile = 2; + // ENTITIES[0].position[0].subtile = 8; + // ENTITIES[0].position[1].tile = 3; + // ENTITIES[0].position[1].subtile = 12; errorOk(); } diff --git a/src/rpg/world/CMakeLists.txt b/src/rpg/world/CMakeLists.txt new file mode 100644 index 0000000..027f8f2 --- /dev/null +++ b/src/rpg/world/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + worldunit.c +) \ No newline at end of file diff --git a/src/rpg/world/worldunit.c b/src/rpg/world/worldunit.c new file mode 100644 index 0000000..97ce1b7 --- /dev/null +++ b/src/rpg/world/worldunit.c @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "worldunit.h" +#include "assert/assert.h" + +void worldChunkPosAdd( + worldchunkpos_t *pos, + const worldsubtile_t amt +) { + int8_t a = pos->subtile; // current signed subtile + int8_t b = amt; // signed delta + uint8_t unsignedAdded = (uint8_t)a + (uint8_t)b; // well-defined wrap add + int8_t r = (int8_t)unsignedAdded; // result in signed domain + + pos->subtile = r; + + // Signed-overflow detection for a + b -> r: + // overflow if sign(a) == sign(b) and sign(r) != sign(a) + uint8_t ov = (uint8_t)((a ^ r) & (b ^ r)) >> 7; // 1 if overflow, else 0 + + // Direction of carry: +1 for b >= 0, -1 for b < 0 (computed branchlessly) + uint8_t neg = ((uint8_t)b) >> 7; // 0 if b>=0, 1 if b<0 + int8_t dir = (int8_t)(1 - (neg << 1)); // +1 or -1 + + // Apply tile adjustment (mod-256 via uint8_t arithmetic) + pos->tile = (uint8_t)(pos->tile + (uint8_t)(ov * (uint8_t)dir)); +} + +float_t worldChunkPosToF32( + const worldchunkpos_t pos, + const uint8_t tileSize +) { + return (float_t)(pos.tile * tileSize) + ((float_t)pos.subtile / 256.0f); +} \ No newline at end of file diff --git a/src/rpg/world/worldunit.h b/src/rpg/world/worldunit.h index 89b5ff0..edb63e6 100644 --- a/src/rpg/world/worldunit.h +++ b/src/rpg/world/worldunit.h @@ -8,13 +8,20 @@ #pragma once #include "dusk.h" +#define WORLD_DIMENSIONS 3 +#define WORLD_SUBTITLE_MIN -128 +#define WORLD_SUBTITLE_MAX 127 + /** * Position in SUBTILE space in a world, each unit represents a single subtile. * This is divided by the size of the tile, e.g. if a tile is 16x16 then there - * are 256 / tile size = units per pixel of a tile. This means there are always - * uint8_t max subtiles in a tile. + * are 128 / tile size = units per pixel of a tile. This means there are always + * between WORLD_SUBTITLE_MIN and WORLD_SUBTITLE_MAX subtiles in a tile. + * + * The extra benefit of this is that different tile sizes don't require any game + * logic updates! */ -typedef uint8_t worldsubtile_t; +typedef int8_t worldsubtile_t; /** * Position in TILE space in a world, each unit represents a single tile. This @@ -51,4 +58,29 @@ typedef struct worldpos_s { worldsubtile_t subtile; worldtile_t tile; worldchunk_t chunk; -} worldpos_t; \ No newline at end of file +} worldpos_t; + +/** + * Adds a number of subtiles to a world chunk position, rolling over into tiles + * and chunks as necessary. + * + * @param pos Pointer to the world chunk position to modify. + * @param amt The amount of subtiles to add (can be negative). + */ +void worldChunkPosAdd( + worldchunkpos_t *pos, + const worldsubtile_t amt +); + +/** + * Converts a world chunk position to a floating point number, given the tile + * size in pixels. + * + * @param pos Pointer to the world chunk position to convert. + * @param tileSize The size of a tile in pixels. + * @return The position as a floating point number. + */ +float_t worldChunkPosToF32( + const worldchunkpos_t pos, + const uint8_t tileSize +); \ No newline at end of file diff --git a/src/scene/scene/scenemap.c b/src/scene/scene/scenemap.c index a50fe17..917d1d8 100644 --- a/src/scene/scene/scenemap.c +++ b/src/scene/scene/scenemap.c @@ -6,9 +6,21 @@ */ #include "scenemap.h" +#include "scene/scenedata.h" #include "display/spritebatch.h" +#include "assert/assert.h" +#include "rpg/entity/entity.h" + +#define TILE_WIDTH 1 +#define TILE_HEIGHT TILE_WIDTH errorret_t sceneMapInit(scenedata_t *data) { + cameraInitPerspective(&data->sceneMap.camera); + + data->sceneMap.camera.lookat.position[0] = 3; + data->sceneMap.camera.lookat.position[1] = 3; + data->sceneMap.camera.lookat.position[2] = 3; + errorOk(); } @@ -16,21 +28,29 @@ void sceneMapUpdate(scenedata_t *data) { } void sceneMapRender(scenedata_t *data) { + cameraPushMatrix(&data->sceneMap.camera); + entity_t *ent = ENTITIES; do { sceneMapRenderEntity(ent); } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); + spriteBatchFlush(); + + cameraPopMatrix(); } void sceneMapRenderEntity(const entity_t *entity) { - assertNotNull(entity); + assertNotNull(entity, "Entity cannot be NULL"); if(entity->type == ENTITY_TYPE_NULL) return; + float_t x = worldChunkPosToF32(entity->position[0], TILE_WIDTH); + float_t y = worldChunkPosToF32(entity->position[1], TILE_HEIGHT); + spriteBatchPush( NULL, - 0.0f, 0.0f, - 32.0f, 32.0f, + x, y, + x + TILE_WIDTH, y + TILE_HEIGHT, COLOR_RED, 0.0f, 0.0f, 1.0f, 1.0f diff --git a/src/scene/scene/scenemap.h b/src/scene/scene/scenemap.h index 41f3208..71f583a 100644 --- a/src/scene/scene/scenemap.h +++ b/src/scene/scene/scenemap.h @@ -8,9 +8,11 @@ #pragma once #include "scene/scene.h" #include "rpg/entity/entity.h" +#include "display/camera.h" typedef struct { int32_t nothing; + camera_t camera; } scenemap_t; errorret_t sceneMapInit(scenedata_t *data);