From 943e775364668e5c1f5b6bdb832d982a9d3587c5 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 9 Nov 2025 18:32:33 -0600 Subject: [PATCH] Time is better. --- src/input/input.c | 76 ++++++++++++++++------------ src/input/input.h | 46 ++++++----------- src/input/inputaction.c | 25 +++------ src/input/inputaction.h | 21 ++++++-- src/rpg/cutscene/item/cutsceneitem.c | 2 +- src/rpg/rpg.c | 4 +- src/rpg/rpgcamera.c | 6 ++- src/rpg/world/CMakeLists.txt | 1 + src/rpg/world/chunk.c | 20 ++++++++ src/rpg/world/chunk.h | 16 +++++- src/rpg/world/map.c | 60 +++++++++++++++++----- src/rpg/world/map.h | 36 +++++++++++-- src/rpg/world/worldpos.h | 1 + src/time/CMakeLists.txt | 1 - src/time/time.c | 46 +++++++++-------- src/time/time.h | 27 ++++++++-- src/ui/uifps.c | 51 +++++++++++++------ 17 files changed, 293 insertions(+), 146 deletions(-) create mode 100644 src/rpg/world/chunk.c diff --git a/src/input/input.c b/src/input/input.c index 329eb79..2c9340f 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -80,12 +80,17 @@ void inputUpdate(void) { // Reset all actions inputactiondata_t *action = &INPUT.actions[0]; do { - action->lastValue = action->currentValue; - action->currentValue = 0.0f; - if(TIME.fixedUpdate) { - action->lastFixedValue = action->currentFixedValue; - action->currentFixedValue = 0.0f; - } + #if TIME_FIXED == 0 + action->lastDynamicValue = action->currentDynamicValue; + action->currentDynamicValue = 0.0f; + if(!TIME.dynamicUpdate) { + action->lastValue = action->currentValue; + action->currentValue = 0.0f; + } + #else + action->lastValue = action->currentValue; + action->currentValue = 0.0f; + #endif action++; } while(action < &INPUT.actions[INPUT_ACTION_COUNT]); @@ -102,50 +107,55 @@ void inputUpdate(void) { } // Update current val. - INPUT.actions[cur->action].currentValue = mathMax( - cur->curVal, INPUT.actions[cur->action].currentValue - ); - if(TIME.fixedUpdate) { - INPUT.actions[cur->action].currentFixedValue = mathMax( - cur->curVal, INPUT.actions[cur->action].currentFixedValue + #if TIME_FIXED == 0 + INPUT.actions[cur->action].currentDynamicValue = mathMax( + cur->curVal, INPUT.actions[cur->action].currentDynamicValue ); - } + if(!TIME.dynamicUpdate) { + INPUT.actions[cur->action].currentValue = mathMax( + cur->curVal, INPUT.actions[cur->action].currentValue + ); + } + #else + INPUT.actions[cur->action].currentValue = mathMax( + cur->curVal, INPUT.actions[cur->action].currentValue + ); + #endif cur++; } while(cur->name); } float_t inputGetCurrentValue(const inputaction_t action) { - // This may be cursed, not sure yet! - if(TIME.fixedUpdate) return inputGetCurrentValueFixed(action); - return inputGetCurrentValueNonFixed(action); -} + #if TIME_FIXED == 0 + if(TIME.dynamicUpdate) return inputGetCurrentValueDynamic(action); + #endif -float_t inputGetCurrentValueFixed(const inputaction_t action) { - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); - return INPUT.actions[action].currentFixedValue; -} - -float_t inputGetCurrentValueNonFixed(const inputaction_t action) { assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].currentValue; } float_t inputGetLastValue(const inputaction_t action) { - if(TIME.fixedUpdate) return inputGetLastValueFixed(action); - return inputGetLastValueNonFixed(action); -} - -float_t inputGetLastValueFixed(const inputaction_t action) { - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); - return INPUT.actions[action].lastFixedValue; -} - -float_t inputGetLastValueNonFixed(const inputaction_t action) { + #if TIME_FIXED == 0 + if(TIME.dynamicUpdate) return inputGetLastValueDynamic(action); + #endif + assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastValue; } +#if TIME_FIXED == 0 + float_t inputGetCurrentValueDynamic(const inputaction_t action) { + assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + return INPUT.actions[action].currentDynamicValue; + } + + float_t inputGetLastValueDynamic(const inputaction_t action) { + assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + return INPUT.actions[action].lastDynamicValue; + } +#endif + bool_t inputIsDown(const inputaction_t action) { return inputGetCurrentValue(action) > 0.0f; } diff --git a/src/input/input.h b/src/input/input.h index 2736a3e..f4fd861 100644 --- a/src/input/input.h +++ b/src/input/input.h @@ -47,22 +47,6 @@ void inputUpdate(void); */ float_t inputGetCurrentValue(const inputaction_t action); -/** - * Gets the current value of a specific input action (fixed timestep). - * - * @param action The input action to get the value for. - * @return The current value of the action (0.0f to 1.0f). - */ -float_t inputGetCurrentValueFixed(const inputaction_t action); - -/** - * Gets the current value of a specific input action (non-fixed timestep). - * - * @param action The input action to get the value for. - * @return The current value of the action (0.0f to 1.0f). - */ -float_t inputGetCurrentValueNonFixed(const inputaction_t action); - /** * Gets the last value of a specific input action. * @@ -71,21 +55,23 @@ float_t inputGetCurrentValueNonFixed(const inputaction_t action); */ float_t inputGetLastValue(const inputaction_t action); -/** - * Gets the last value of a specific input action (fixed timestep). - * - * @param action The input action to get the value for. - * @return The last value of the action (0.0f to 1.0f). - */ -float_t inputGetLastValueFixed(const inputaction_t action); +#if TIME_FIXED == 0 + /** + * Gets the current value of a specific input action (dynamic timestep). + * + * @param action The input action to get the value for. + * @return The current value of the action (0.0f to 1.0f). + */ + float_t inputGetCurrentValueDynamic(const inputaction_t action); -/** - * Gets the last value of a specific input action (non-fixed timestep). - * - * @param action The input action to get the value for. - * @return The last value of the action (0.0f to 1.0f). - */ -float_t inputGetLastValueNonFixed(const inputaction_t action); + /** + * Gets the last value of a specific input action (dynamic timestep). + * + * @param action The input action to get the value for. + * @return The last value of the action (0.0f to 1.0f). + */ + float_t inputGetLastValueDynamic(const inputaction_t action); +#endif /** * Checks if a specific input action is currently pressed. diff --git a/src/input/inputaction.c b/src/input/inputaction.c index b795d14..db84312 100644 --- a/src/input/inputaction.c +++ b/src/input/inputaction.c @@ -9,22 +9,13 @@ #include "assert/assert.h" #include "util/string.h" -const char_t* INPUT_ACTION_NAMES[INPUT_ACTION_COUNT] = { - [INPUT_ACTION_UP] = "UP", - [INPUT_ACTION_DOWN] = "DOWN", - [INPUT_ACTION_LEFT] = "LEFT", - [INPUT_ACTION_RIGHT] = "RIGHT", - [INPUT_ACTION_ACCEPT] = "ACCEPT", - [INPUT_ACTION_CANCEL] = "CANCEL", -}; - -inputaction_t inputActionGetByName(const char_t *name) { - assertNotNull(name, "name must not be NULL"); +// inputaction_t inputActionGetByName(const char_t *name) { +// assertNotNull(name, "name must not be NULL"); - for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) { - if(stringCompareInsensitive(INPUT_ACTION_NAMES[i], name) != 0) continue; - return i; - } +// for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) { +// if(stringCompareInsensitive(INPUT_ACTION_NAMES[i], name) != 0) continue; +// return i; +// } - return INPUT_ACTION_COUNT; -} \ No newline at end of file +// return INPUT_ACTION_COUNT; +// } \ No newline at end of file diff --git a/src/input/inputaction.h b/src/input/inputaction.h index 1040e2c..6937bb4 100644 --- a/src/input/inputaction.h +++ b/src/input/inputaction.h @@ -6,7 +6,7 @@ */ #pragma once -#include "dusk.h" +#include "time/time.h" typedef enum { INPUT_ACTION_NULL, @@ -25,11 +25,22 @@ typedef struct { inputaction_t action; float_t lastValue; float_t currentValue; - float_t lastFixedValue; - float_t currentFixedValue; + + #if TIME_FIXED == 0 + float_t lastDynamicValue; + float_t currentDynamicValue; + #endif } inputactiondata_t; -extern const char_t* INPUT_ACTION_NAMES[INPUT_ACTION_COUNT]; +// static const char_t* INPUT_ACTION_NAMES[INPUT_ACTION_COUNT] = { +// [INPUT_ACTION_UP] = "UP", +// [INPUT_ACTION_DOWN] = "DOWN", +// [INPUT_ACTION_LEFT] = "LEFT", +// [INPUT_ACTION_RIGHT] = "RIGHT", +// [INPUT_ACTION_ACCEPT] = "ACCEPT", +// [INPUT_ACTION_CANCEL] = "CANCEL", +// [INPUT_ACTION_RAGEQUIT] = "RAGEQUIT", +// }; /** * Gets an input action by its name. @@ -37,4 +48,4 @@ extern const char_t* INPUT_ACTION_NAMES[INPUT_ACTION_COUNT]; * @param name The name of the input action. * @return The input action, or INPUT_ACTION_COUNT if not found. */ -inputaction_t inputActionGetByName(const char_t *name); \ No newline at end of file +// inputaction_t inputActionGetByName(const char_t *name); \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutsceneitem.c b/src/rpg/cutscene/item/cutsceneitem.c index 08d004a..18a8b58 100644 --- a/src/rpg/cutscene/item/cutsceneitem.c +++ b/src/rpg/cutscene/item/cutsceneitem.c @@ -44,7 +44,7 @@ void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { break; case CUTSCENE_ITEM_TYPE_WAIT: - data->wait -= TIME.fixedDelta; + data->wait -= TIME.delta; if(data->wait <= 0) cutsceneSystemNext(); break; diff --git a/src/rpg/rpg.c b/src/rpg/rpg.c index 9b848f3..b6ad820 100644 --- a/src/rpg/rpg.c +++ b/src/rpg/rpg.c @@ -42,7 +42,9 @@ errorret_t rpgInit(void) { } void rpgUpdate(void) { - if(!TIME.fixedUpdate) return; + #if TIME_FIXED == 0 + if(TIME.dynamicUpdate) return; + #endif // TODO: Do not update if the scene is not the map scene? mapUpdate(); diff --git a/src/rpg/rpgcamera.c b/src/rpg/rpgcamera.c index 4926e04..96688be 100644 --- a/src/rpg/rpgcamera.c +++ b/src/rpg/rpgcamera.c @@ -19,7 +19,11 @@ void rpgCameraInit(void) { void rpgCameraUpdate(void) { switch(RPG_CAMERA.mode) { case RPG_CAMERA_MODE_FREE: - // Free camera mode; nothing to do. + mapPositionSet( + (int16_t)(RPG_CAMERA.free.x / CHUNK_WIDTH) - (MAP_CHUNK_WIDTH / 2), + (int16_t)(RPG_CAMERA.free.y / CHUNK_HEIGHT) - (MAP_CHUNK_HEIGHT / 2), + (int16_t)(RPG_CAMERA.free.z / CHUNK_DEPTH) - (MAP_CHUNK_DEPTH / 2) + ); break; case RPG_CAMERA_MODE_FOLLOW_ENTITY: { diff --git a/src/rpg/world/CMakeLists.txt b/src/rpg/world/CMakeLists.txt index ea50569..f38a5ec 100644 --- a/src/rpg/world/CMakeLists.txt +++ b/src/rpg/world/CMakeLists.txt @@ -6,5 +6,6 @@ # Sources target_sources(${DUSK_TARGET_NAME} PRIVATE + chunk.c map.c ) \ No newline at end of file diff --git a/src/rpg/world/chunk.c b/src/rpg/world/chunk.c new file mode 100644 index 0000000..3ef63d1 --- /dev/null +++ b/src/rpg/world/chunk.c @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "chunk.h" + +uint32_t chunkGetTileIndex( + const uint8_t relativeTileX, + const uint8_t relativeTileY, + const uint8_t relativeTileZ +) { + return ( + (relativeTileZ * CHUNK_WIDTH * CHUNK_HEIGHT) + + (relativeTileY * CHUNK_WIDTH) + + relativeTileX + ); +} \ No newline at end of file diff --git a/src/rpg/world/chunk.h b/src/rpg/world/chunk.h index 31a0004..d1a016b 100644 --- a/src/rpg/world/chunk.h +++ b/src/rpg/world/chunk.h @@ -16,4 +16,18 @@ typedef struct chunk_s { int16_t x, y, z; tile_t tiles[CHUNK_TILE_COUNT]; -} chunk_t; \ No newline at end of file +} chunk_t; + +/** + * Gets the tile index for a tile position within a chunk. + * + * @param relativeTileX The X coordinate of the tile within the chunk. + * @param relativeTileY The Y coordinate of the tile within the chunk. + * @param relativeTileZ The Z coordinate of the tile within the chunk. + * @return The tile index within the chunk. + */ +uint32_t chunkGetTileIndex( + const uint8_t relativeTileX, + const uint8_t relativeTileY, + const uint8_t relativeTileZ +); \ No newline at end of file diff --git a/src/rpg/world/map.c b/src/rpg/world/map.c index 907d147..0bafd0e 100644 --- a/src/rpg/world/map.c +++ b/src/rpg/world/map.c @@ -7,24 +7,14 @@ #include "map.h" #include "util/memory.h" -#include // For printf map_t MAP; -// Dummy functions for chunk loading/unloading -void mapChunkUnload(chunk_t* chunk) { - // Placeholder for unloading logic - printf("Unloading chunk at (%d, %d, %d)\n", chunk->x, chunk->y, chunk->z); -} - -void mapChunkLoad(chunk_t* chunk) { - // Placeholder for loading logic - printf("Loading chunk at (%d, %d, %d)\n", chunk->x, chunk->y, chunk->z); -} - void mapInit() { memoryZero(&MAP, sizeof(map_t)); + // Init the default chunks. In future I'll probably make this based on where + // the player spawns in to save an initial mapSet. uint32_t index = 0; for(uint32_t z = 0; z < MAP_CHUNK_DEPTH; z++) { for(uint32_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { @@ -122,4 +112,50 @@ void mapPositionSet(const int16_t x, const int16_t y, const int16_t z) { void mapUpdate() { +} +void mapChunkUnload(chunk_t* chunk) { +} + +void mapChunkLoad(chunk_t* chunk) { + memoryZero(chunk->tiles, sizeof(tile_t) * CHUNK_TILE_COUNT); + + uint8_t x, y, z; + x = 1; + y = 2; + z = 0; + + chunk->tiles[ + (z * CHUNK_WIDTH * CHUNK_HEIGHT) + + (y * CHUNK_WIDTH) + + x + ] = (tile_t){ .id = 1 }; +} + +uint8_t mapGetChunkIndexAt( + const int16_t chunkX, + const int16_t chunkY, + const int16_t chunkZ +) { + int16_t relX = chunkX - MAP.x; + int16_t relY = chunkY - MAP.y; + int16_t relZ = chunkZ - MAP.z; + + if( + relX < 0 || relX >= MAP_CHUNK_WIDTH || + relY < 0 || relY >= MAP_CHUNK_HEIGHT || + relZ < 0 || relZ >= MAP_CHUNK_DEPTH + ) { + return UINT8_MAX; + } + + return ( + (relZ * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) + + (relY * MAP_CHUNK_WIDTH) + + relX + ); +} + +chunk_t* mapGetChunkByIndex(const uint8_t index) { + if(index >= MAP_CHUNK_COUNT) return NULL; + return &MAP.chunks[index]; } \ No newline at end of file diff --git a/src/rpg/world/map.h b/src/rpg/world/map.h index 62576a0..9796cb8 100644 --- a/src/rpg/world/map.h +++ b/src/rpg/world/map.h @@ -33,15 +33,45 @@ void mapUpdate(); /** * Sets the map position and updates chunks accordingly. + * + * @param x The new X position. + * @param y The new Y position. + * @param z The new Z position. */ void mapPositionSet(const int16_t x, const int16_t y, const int16_t z); /** - * Dummy: Unloads a chunk. + * Unloads a chunk. + * + * @param chunk The chunk to unload. */ void mapChunkUnload(chunk_t* chunk); /** - * Dummy: Loads a chunk. + * Loads a chunk. + * + * @param chunk The chunk to load. */ -void mapChunkLoad(chunk_t* chunk); \ No newline at end of file +void mapChunkLoad(chunk_t* chunk); + +/** + * Gets the index of a chunk at the specified CHUNK coordinates. + * + * @param chunkX The X coordinate of the chunk (in CHUNK units). + * @param chunkY The Y coordinate of the chunk (in CHUNK units). + * @param chunkZ The Z coordinate of the chunk (in CHUNK units). + * @return The index of the chunk, or UINT8_MAX if out of bounds. + */ +uint8_t mapGetChunkIndexAt( + const int16_t chunkX, + const int16_t chunkY, + const int16_t chunkZ +); + +/** + * Gets a chunk by its index. + * + * @param chunkIndex The index of the chunk. + * @return A pointer to the chunk. + */ +chunk_t * mapGetChunk(const uint8_t chunkIndex); \ No newline at end of file diff --git a/src/rpg/world/worldpos.h b/src/rpg/world/worldpos.h index 81f91bb..935f349 100644 --- a/src/rpg/world/worldpos.h +++ b/src/rpg/world/worldpos.h @@ -9,6 +9,7 @@ #include "dusk.h" typedef uint8_t worldunit_t; +typedef int16_t chunkunit_t; typedef int8_t worldunits_t; typedef struct worldpos_s { diff --git a/src/time/CMakeLists.txt b/src/time/CMakeLists.txt index 7b5cf65..80797bd 100644 --- a/src/time/CMakeLists.txt +++ b/src/time/CMakeLists.txt @@ -19,6 +19,5 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp") target_compile_definitions(${DUSK_TARGET_NAME} PRIVATE TIME_FIXED=1 - TIME_PLATFORM_STEP=0.016 ) endif() \ No newline at end of file diff --git a/src/time/time.c b/src/time/time.c index 8882212..de12317 100644 --- a/src/time/time.c +++ b/src/time/time.c @@ -22,31 +22,33 @@ void timeInit(void) { TIME.time = TIME_STEP; TIME.delta = TIME_STEP; - TIME.fixedDelta = TIME_STEP; - TIME.fixedTime = TIME_STEP; + #if TIME_FIXED == 0 + TIME.dynamicTime = TIME_STEP; + TIME.dynamicDelta = TIME_STEP; + TIME.dynamicUpdate = false; + #endif } void timeUpdate(void) { - float_t delta; - #if TIME_SDL2 - delta = (float_t)SDL_GetTicks() / 1000.0f - TIME.time; - #elif TIME_FIXED - delta = TIME_PLATFORM_STEP; + + #if TIME_FIXED == 0 + #if TIME_SDL2 + float_t newTime = (float_t)SDL_GetTicks() / 1000.0f; + TIME.dynamicDelta = newTime - TIME.dynamicTime; + TIME.dynamicTime = newTime; + TIME.dynamicUpdate = true; + #else + #error "No time platform defined" + #endif + + assertTrue(TIME.dynamicDelta >= 0.0f, "Time delta is negative"); + if(TIME.dynamicTime - TIME.time >= TIME_STEP) { + TIME.dynamicUpdate = false; + TIME.delta = TIME_STEP; + TIME.time += TIME_STEP; + } #else - #error "No time platform defined" + TIME.delta = TIME_STEP; + TIME.time += TIME_STEP; #endif - - TIME.delta = delta; - assertTrue(TIME.delta >= 0.0f, "Time delta is negative"); - TIME.time += TIME.delta; - - // Perform a fixed time step. - if(TIME.time - TIME.fixedTime >= (TIME_STEP * 0.9f)) { - TIME.fixedUpdate = true; - TIME.fixedDelta = TIME_STEP; - TIME.fixedTime += TIME_STEP; - } else { - TIME.fixedDelta = 0.0f; - TIME.fixedUpdate = false; - } } \ No newline at end of file diff --git a/src/time/time.h b/src/time/time.h index 354af4f..2547a45 100644 --- a/src/time/time.h +++ b/src/time/time.h @@ -8,18 +8,37 @@ #pragma once #include "dusk.h" +#define TIME_STEP (1.0f / 60.0f) // 60 Ticks per second. + +#ifndef TIME_FIXED + #define TIME_FIXED 0 +#else + #ifndef TIME_PLATFORM_STEP + #error "TIME_PLATFORM_STEP must be defined when TIME_FIXED is enabled" + #endif + + #if TIME_PLATFORM_STEP <= 0.0f + #error "TIME_PLATFORM_STEP must be greater than zero" + #endif + + #if TIME_PLATFORM_STEP != TIME_STEP + #define TIME_FIXED 0 + #endif +#endif + typedef struct { float_t delta; float_t time; - bool_t fixedUpdate; - float_t fixedDelta; - float_t fixedTime; + #if TIME_FIXED == 0 + bool_t dynamicUpdate; + float_t dynamicDelta; + float_t dynamicTime; + #endif } dusktime_t; extern dusktime_t TIME; -#define TIME_STEP (1.0f / 60.0f) // 60 Ticks per second. /** * Initializes the time system. diff --git a/src/ui/uifps.c b/src/ui/uifps.c index f784c63..82e9a9e 100644 --- a/src/ui/uifps.c +++ b/src/ui/uifps.c @@ -17,21 +17,42 @@ bool_t UI_FPS_DRAW = true; void uiFPSRender(const tileset_t *tileset, texture_t *texture) { if(!UI_FPS_DRAW) return; - float_t fps = TIME.delta > 0.0f ? (1.0f / TIME.delta) : 0.0f; - char_t buffer[64]; - snprintf( - buffer, - sizeof(buffer), - "%.2f/%d", - TIME.delta * 1000.0f, - (int32_t)fps - ); - - color_t color = ( - fps >= 50.0f ? COLOR_GREEN : - fps >= 30.0f ? COLOR_YELLOW : - COLOR_RED - ); + char_t buffer[96]; + color_t color; + + #if TIME_FIXED == 0 + float_t fpsDynamic = TIME.dynamicDelta > 0.0f ? (1.0f / TIME.dynamicDelta) : 0.0f; + float_t fpsFixed = TIME.delta > 0.0f ? (1.0f / TIME.delta) : 0.0f; + snprintf( + buffer, + sizeof(buffer), + "%.2f/%.2f/%d", + TIME.dynamicDelta * 1000.0f, + TIME.delta * 1000.0f, + (int32_t)fpsDynamic + ); + + color = ( + fpsDynamic >= 50.0f ? COLOR_GREEN : + fpsDynamic >= 30.0f ? COLOR_YELLOW : + COLOR_RED + ); + #else + float_t fps = TIME.delta > 0.0f ? (1.0f / TIME.delta) : 0.0f; + snprintf( + buffer, + sizeof(buffer), + "%.2f/%d", + TIME.delta * 1000.0f, + (int32_t)fps + ); + + color = ( + fps >= 50.0f ? COLOR_GREEN : + fps >= 30.0f ? COLOR_YELLOW : + COLOR_RED + ); + #endif int32_t w, h; uiTextMeasure(buffer, tileset, &w, &h);