diff --git a/CLAUDE.md b/CLAUDE.md index ed7bea5e..451c4d16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -423,6 +423,29 @@ above the declaration with no blank line in between. --- +## Color system + +Colors are defined in `src/dusk/display/color.csv` and code-generated +into a `color.h` header by `tools/color/csv/__main__.py`. + +Each row in the CSV has `name,r,g,b,a` with channel values in `[0.0, 1.0]`. +The script emits four `#define` variants per color plus a bare alias: + +``` +COLOR__4B color4b(r8, g8, b8, a8) // default alias target +COLOR__3B color3b(r8, g8, b8) +COLOR__3F color3f(rf, gf, bf) +COLOR__4F color4f(rf, gf, bf, af) +COLOR_ COLOR__4B +``` + +`color_t` is `color4b_t` (four `uint8_t` channels). + +To add a new color, append a row to `color.csv` and rebuild — do not +hand-edit the generated header. + +--- + ## Tests - Tests live in `test/` mirroring `src/dusk/` structure. - Use cmocka; include `dusktest.h`. diff --git a/src/dusk/rpg/entity/CMakeLists.txt b/src/dusk/rpg/entity/CMakeLists.txt index a254c108..8a249fd4 100644 --- a/src/dusk/rpg/entity/CMakeLists.txt +++ b/src/dusk/rpg/entity/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} entityanim.c entitydir.c entityinteract.c - npc.c player.c -) \ No newline at end of file +) + +add_subdirectory(npc) \ No newline at end of file diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index 274ac576..85d00fb6 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -41,6 +41,9 @@ void entityUpdate(entity_t *entity) { entityAnimUpdate(entity); // Movement code. + if(ENTITY_CALLBACKS[entity->type].freeMovement != NULL) { + ENTITY_CALLBACKS[entity->type].freeMovement(entity); + } if( cutsceneModeIsInputAllowed() && ENTITY_CALLBACKS[entity->type].movement != NULL diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h index 6d6a566a..544e341c 100644 --- a/src/dusk/rpg/entity/entity.h +++ b/src/dusk/rpg/entity/entity.h @@ -10,7 +10,7 @@ #include "entityanim.h" #include "entityinteract.h" #include "entitytype.h" -#include "npc.h" +#include "npc/npc.h" typedef struct map_s map_t; diff --git a/src/dusk/rpg/entity/entityanim.h b/src/dusk/rpg/entity/entityanim.h index 91390682..9c9de54e 100644 --- a/src/dusk/rpg/entity/entityanim.h +++ b/src/dusk/rpg/entity/entityanim.h @@ -9,8 +9,8 @@ #include "time/time.h" #define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(2) -#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(6) -#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(3) +#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(10) +#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(6) typedef struct entity_s entity_t; diff --git a/src/dusk/rpg/entity/entitytype.h b/src/dusk/rpg/entity/entitytype.h index fb67f33c..4cfffe21 100644 --- a/src/dusk/rpg/entity/entitytype.h +++ b/src/dusk/rpg/entity/entitytype.h @@ -7,7 +7,7 @@ #pragma once #include "rpg/entity/player.h" -#include "npc.h" +#include "npc/npc.h" typedef enum { ENTITY_TYPE_NULL, @@ -33,11 +33,17 @@ typedef struct { void (*init)(entity_t *entity); /** - * Movement callback for the entity type. + * Movement callback for the entity type. Gated by cutscene input. * @param entity Pointer to the entity to move. */ void (*movement)(entity_t *entity); + /** + * Free movement callback. Always runs regardless of cutscene state. + * @param entity Pointer to the entity to move. + */ + void (*freeMovement)(entity_t *entity); + /** * Interaction callback for the entity type. * @param player Pointer to the player entity. @@ -58,6 +64,7 @@ static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = { [ENTITY_TYPE_NPC] = { .init = npcInit, .movement = npcMovement, + .freeMovement = npcFreeMovement, .interact = npcInteract } }; \ No newline at end of file diff --git a/src/dusk/rpg/entity/npc.c b/src/dusk/rpg/entity/npc.c deleted file mode 100644 index ec6548bd..00000000 --- a/src/dusk/rpg/entity/npc.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" - -#include "rpg/cutscene/scene/testcutscene.h" -#include "rpg/rpgtextbox.h" - -void npcInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -void npcMovement(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -bool_t npcInteract(entity_t *player, entity_t *npc) { - assertNotNull(player, "Player entity pointer cannot be NULL"); - assertNotNull(npc, "NPC entity pointer cannot be NULL"); - - cutsceneSystemStartCutscene(&TEST_CUTSCENE); - - // rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!"); - - return false; -}; \ No newline at end of file diff --git a/src/dusk/rpg/entity/npc.h b/src/dusk/rpg/entity/npc.h deleted file mode 100644 index df36f15e..00000000 --- a/src/dusk/rpg/entity/npc.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct entity_s entity_t; - -typedef struct { - void *nothing; -} npc_t; - -/** - * Initializes an NPC entity. - * - * @param entity Pointer to the entity structure to initialize. - */ -void npcInit(entity_t *entity); - -/** - * Updates an NPC entity. - * - * @param entity Pointer to the entity structure to update. - */ -void npcMovement(entity_t *entity); - -/** - * Handles interaction with an NPC entity. - * - * @param player Pointer to the player entity. - * @param npc Pointer to the NPC entity. - */ -bool_t npcInteract(entity_t *player, entity_t *npc); \ No newline at end of file diff --git a/src/dusk/rpg/entity/npc/CMakeLists.txt b/src/dusk/rpg/entity/npc/CMakeLists.txt new file mode 100644 index 00000000..922a4e1d --- /dev/null +++ b/src/dusk/rpg/entity/npc/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + npc.c + npcturn.c + npcwalk.c + npcpath.c +) diff --git a/src/dusk/rpg/entity/npc/npc.c b/src/dusk/rpg/entity/npc/npc.c new file mode 100644 index 00000000..a8dcf6a5 --- /dev/null +++ b/src/dusk/rpg/entity/npc/npc.c @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/entity/entity.h" +#include "assert/assert.h" + +#include "rpg/cutscene/scene/testcutscene.h" +#include "rpg/rpgtextbox.h" + +const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = { + [NPC_MOVE_TYPE_NULL] = { NULL, NULL }, + [NPC_MOVE_TYPE_RANDOM_TURN] = { npcRandomTurnInit, npcRandomTurnMovement }, + [NPC_MOVE_TYPE_RANDOM_WALK] = { + npcRandomWalkInit, + npcRandomWalkMovement + }, + [NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK] = { + npcRandomTurnAndWalkInit, + npcRandomTurnAndWalkMovement + }, + [NPC_MOVE_TYPE_PATH] = { npcPathInit, npcPathMovement, true }, +}; + +void npcInit(entity_t *entity) { + assertNotNull(entity, "Entity pointer cannot be NULL"); +} + +void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + npc_t *npc = &entity->data.npc; + npc->moveType = moveType; + if(NPC_MOVE_CALLBACKS[moveType].init != NULL) { + NPC_MOVE_CALLBACKS[moveType].init(npc); + } +} + +void npcMovement(entity_t *entity) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + npc_t *npc = &entity->data.npc; + const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType]; + if(!cb->alwaysRun && cb->movement != NULL) cb->movement(entity); +} + +void npcFreeMovement(entity_t *entity) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + npc_t *npc = &entity->data.npc; + const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType]; + if(cb->alwaysRun && cb->movement != NULL) cb->movement(entity); +} + +bool_t npcInteract(entity_t *player, entity_t *npc) { + assertNotNull(player, "Player entity pointer cannot be NULL"); + assertNotNull(npc, "NPC entity pointer cannot be NULL"); + + cutsceneSystemStartCutscene(&TEST_CUTSCENE); + // rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!"); + return false; +} diff --git a/src/dusk/rpg/entity/npc/npc.h b/src/dusk/rpg/entity/npc/npc.h new file mode 100644 index 00000000..244f2b98 --- /dev/null +++ b/src/dusk/rpg/entity/npc/npc.h @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "npcturn.h" +#include "npcwalk.h" +#include "npcpath.h" + +typedef struct entity_s entity_t; + +typedef enum { + NPC_MOVE_TYPE_NULL, + NPC_MOVE_TYPE_RANDOM_TURN, + NPC_MOVE_TYPE_RANDOM_WALK, + NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK, + NPC_MOVE_TYPE_PATH, + NPC_MOVE_TYPE_COUNT +} npcmovetype_t; + +typedef union { + npcrandomturn_t randomTurn; + npcrandomwalk_t randomWalk; + npcrandomturnandwalk_t randomTurnAndWalk; + npcpath_t path; +} npcmovedata_t; + +typedef struct npc_s { + npcmovetype_t moveType; + npcmovedata_t moveData; +} npc_t; + +typedef struct { + /** Called once when the move type is set. */ + void (*init)(npc_t *npc); + /** Called each movement tick. */ + void (*movement)(entity_t *entity); + /** True if movement runs regardless of cutscene state. */ + bool_t alwaysRun; +} npcmovecallback_t; + +extern const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT]; + +/** + * Initializes an NPC entity. + * + * @param entity Pointer to the entity structure to initialize. + */ +void npcInit(entity_t *entity); + +/** + * Sets the movement type for an NPC entity. + * + * @param entity Pointer to the entity structure. + * @param moveType The movement type to set. + */ +void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType); + +/** + * Movement callback for an NPC entity. Gated by cutscene input. + * + * @param entity Pointer to the entity structure to update. + */ +void npcMovement(entity_t *entity); + +/** + * Free movement callback for an NPC entity. Runs always-run move types + * regardless of cutscene state. + * + * @param entity Pointer to the entity structure to update. + */ +void npcFreeMovement(entity_t *entity); + +/** + * Handles interaction with an NPC entity. + * + * @param player Pointer to the player entity. + * @param npc Pointer to the NPC entity. + */ +bool_t npcInteract(entity_t *player, entity_t *npc); diff --git a/src/dusk/rpg/entity/npc/npcpath.c b/src/dusk/rpg/entity/npc/npcpath.c new file mode 100644 index 00000000..741ea89d --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcpath.c @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "npc.h" +#include "rpg/entity/entity.h" + +void npcPathInit(npc_t *npc) { + npcpath_t *path = &npc->moveData.path; + path->count = 0; + path->index = 0; +} + +void npcPathMovement(entity_t *entity) { + npcpath_t *path = &entity->data.npc.moveData.path; + if(path->count == 0) return; + if(!entityCanWalk(entity)) return; + + worldpos_t *target = &path->positions[path->index]; + + // At this waypoint - advance to the next + if( + entity->position.x == target->x && + entity->position.y == target->y + ) { + path->index = (path->index + 1) % path->count; + return; + } + + entitydir_t dir; + if(entity->position.x != target->x) { + dir = entity->position.x < target->x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST; + } else { + dir = entity->position.y < target->y ? ENTITY_DIR_SOUTH : ENTITY_DIR_NORTH; + } + + entityWalk(entity, dir); +} diff --git a/src/dusk/rpg/entity/npc/npcpath.h b/src/dusk/rpg/entity/npc/npcpath.h new file mode 100644 index 00000000..5af51d73 --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcpath.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "rpg/overworld/worldpos.h" + +typedef struct npc_s npc_t; +typedef struct entity_s entity_t; + +/** Maximum number of waypoints in an NPC path. */ +#define NPC_PATH_COUNT_MAX 8 + +typedef struct { + worldpos_t positions[NPC_PATH_COUNT_MAX]; + uint8_t count; + uint8_t index; +} npcpath_t; + +/** + * Initializes the path movement data for an NPC. + * + * @param npc Pointer to the NPC to initialize. + */ +void npcPathInit(npc_t *npc); + +/** + * Movement tick for an NPC following a path. + * + * @param entity Pointer to the entity to update. + */ +void npcPathMovement(entity_t *entity); diff --git a/src/dusk/rpg/entity/npc/npcturn.c b/src/dusk/rpg/entity/npc/npcturn.c new file mode 100644 index 00000000..63654ab3 --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcturn.c @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "npc.h" +#include "rpg/entity/entity.h" +#include "util/random.h" +#include "time/time.h" +#include + +void npcRandomTurnInit(npc_t *npc) { + npcrandomturn_t *turn = &npc->moveData.randomTurn; + turn->frequencyMin = NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT; + turn->frequencyMax = NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT; + turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax); +} + +void npcRandomTurnMovement(entity_t *entity) { + npcrandomturn_t *turn = &entity->data.npc.moveData.randomTurn; + turn->timer -= TIME.delta; + if(turn->timer > 0.0f) return; + turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax); + if(entityCanTurn(entity)) entityTurn(entity, (entitydir_t)(rand() % 4)); +} diff --git a/src/dusk/rpg/entity/npc/npcturn.h b/src/dusk/rpg/entity/npc/npcturn.h new file mode 100644 index 00000000..3568bbdb --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcturn.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct npc_s npc_t; +typedef struct entity_s entity_t; + +/** Default min/max seconds between NPC random-turn ticks. */ +#define NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT 2.0f +#define NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT 4.0f + +typedef struct { + float_t frequencyMin; + float_t frequencyMax; + float_t timer; +} npcrandomturn_t; + +/** + * Initializes the random-turn movement data for an NPC. + * + * @param npc Pointer to the NPC to initialize. + */ +void npcRandomTurnInit(npc_t *npc); + +/** + * Movement tick for an NPC using random-turn movement. + * + * @param entity Pointer to the entity to update. + */ +void npcRandomTurnMovement(entity_t *entity); diff --git a/src/dusk/rpg/entity/npc/npcwalk.c b/src/dusk/rpg/entity/npc/npcwalk.c new file mode 100644 index 00000000..dc8794ec --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcwalk.c @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "npc.h" +#include "rpg/entity/entity.h" +#include "util/random.h" +#include "time/time.h" +#include + +void npcRandomWalkInit(npc_t *npc) { + npcrandomwalk_t *walk = &npc->moveData.randomWalk; + walk->frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT; + walk->frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT; + walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax); +} + +void npcRandomWalkMovement(entity_t *entity) { + npcrandomwalk_t *walk = &entity->data.npc.moveData.randomWalk; + walk->timer -= TIME.delta; + if(walk->timer > 0.0f) return; + walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax); + if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4)); +} + +void npcRandomTurnAndWalkInit(npc_t *npc) { + npcRandomTurnInit(npc); + npcrandomturnandwalk_t *tw = &npc->moveData.randomTurnAndWalk; + tw->walk.frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT; + tw->walk.frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT; + tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax); +} + +void npcRandomTurnAndWalkMovement(entity_t *entity) { + npcrandomturnandwalk_t *tw = &entity->data.npc.moveData.randomTurnAndWalk; + + tw->turn.timer -= TIME.delta; + if(tw->turn.timer <= 0.0f) { + tw->turn.timer = randomFloat(tw->turn.frequencyMin, tw->turn.frequencyMax); + if(entityCanTurn(entity)) entityTurn(entity, (entitydir_t)(rand() % 4)); + } + + tw->walk.timer -= TIME.delta; + if(tw->walk.timer <= 0.0f) { + tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax); + if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4)); + } +} + diff --git a/src/dusk/rpg/entity/npc/npcwalk.h b/src/dusk/rpg/entity/npc/npcwalk.h new file mode 100644 index 00000000..4a65c4f8 --- /dev/null +++ b/src/dusk/rpg/entity/npc/npcwalk.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "npcturn.h" + +/** Default min/max seconds between NPC random-walk ticks. */ +#define NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT 2.0f +#define NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT 5.0f + +typedef struct { + float_t frequencyMin; + float_t frequencyMax; + float_t timer; +} npcrandomwalk_t; + +typedef struct { + npcrandomturn_t turn; + npcrandomwalk_t walk; +} npcrandomturnandwalk_t; + +/** + * Initializes the random-walk movement data for an NPC. + * + * @param npc Pointer to the NPC to initialize. + */ +void npcRandomWalkInit(npc_t *npc); + +/** + * Movement tick for an NPC using random-walk movement. + * + * @param entity Pointer to the entity to update. + */ +void npcRandomWalkMovement(entity_t *entity); + +/** + * Initializes the random-turn-and-walk movement data for an NPC. + * + * @param npc Pointer to the NPC to initialize. + */ +void npcRandomTurnAndWalkInit(npc_t *npc); + +/** + * Movement tick for an NPC using random-turn-and-walk movement. + * + * @param entity Pointer to the entity to update. + */ +void npcRandomTurnAndWalkMovement(entity_t *entity); + diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index c2e7446d..b4f38c0b 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -41,10 +41,18 @@ errorret_t rpgInit(void) { uint8_t npcIndex = entityGetAvailable(); entity_t *npc = &ENTITIES[npcIndex]; entityInit(npc, ENTITY_TYPE_NPC); + npcSetMoveType(npc, NPC_MOVE_TYPE_PATH); npc->position = (worldpos_t){ 3, 3, 0 }; npc->interact.type = ENTITY_INTERACT_PRINT; npc->interact.data.message = "hello world"; + npcpath_t *path = &npc->data.npc.moveData.path; + path->positions[0] = (worldpos_t){ 3, 3, 0 }; + path->positions[1] = (worldpos_t){ 5, 3, 0 }; + path->positions[2] = (worldpos_t){ 5, 5, 0 }; + path->positions[3] = (worldpos_t){ 3, 5, 0 }; + path->count = 4; + // All Good! errorOk(); } diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index e75e8def..064a4748 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -109,35 +109,30 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { // Entities { - uint8_t spriteCount = 0; - spritebatchsprite_t sprites[ENTITY_COUNT]; for(uint8_t i = 0; i < ENTITY_COUNT; i++) { entity_t *ent = &ENTITIES[i]; if(ent->type == ENTITY_TYPE_NULL) continue; - glm_vec3_copy(ent->renderPosition, sprites[spriteCount].min); - glm_vec3_copy(ent->renderPosition, sprites[spriteCount].max); - glm_vec3_add( - sprites[spriteCount].max, - (vec3){ 1, 1, 0 }, - sprites[spriteCount].max - ); + spritebatchsprite_t sprite; + glm_vec3_copy(ent->renderPosition, sprite.min); + glm_vec3_copy(ent->renderPosition, sprite.max); + glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max); + glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin); + glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax); - glm_vec2_copy((vec2){ 0, 0 }, sprites[spriteCount].uvMin); - glm_vec2_copy((vec2){ 1, 1 }, sprites[spriteCount].uvMax); + color_t color; + switch(ent->direction) { + case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break; + case ENTITY_DIR_EAST: color = COLOR_RED; break; + case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break; + case ENTITY_DIR_WEST: color = COLOR_BLUE; break; + default: color = COLOR_CYAN; break; + } - spriteCount++; - } - - if(spriteCount) { shadermaterial_t material = { - .unlit = { - .color = COLOR_CYAN, - .texture = NULL - } + .unlit = { .color = color, .texture = NULL } }; - // material.unlit.texture = &TEXTURE_TEST; - spriteBatchBuffer(sprites, spriteCount, &SHADER_UNLIT, material); + spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material); spriteBatchFlush(); } } diff --git a/src/dusk/util/CMakeLists.txt b/src/dusk/util/CMakeLists.txt index 1beed776..62fb7902 100644 --- a/src/dusk/util/CMakeLists.txt +++ b/src/dusk/util/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} crypt.c endian.c memory.c + random.c string.c math.c sort.c diff --git a/src/dusk/util/random.c b/src/dusk/util/random.c new file mode 100644 index 00000000..ded6b7d0 --- /dev/null +++ b/src/dusk/util/random.c @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "random.h" +#include + +float_t randomFloat(const float_t min, const float_t max) { + return min + ((float_t)rand() / (float_t)RAND_MAX) * (max - min); +} diff --git a/src/dusk/util/random.h b/src/dusk/util/random.h new file mode 100644 index 00000000..3968e2af --- /dev/null +++ b/src/dusk/util/random.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +/** + * Returns a random float between min (inclusive) and max (exclusive). + * + * @param min Lower bound. + * @param max Upper bound. + * @returns A random float in [min, max). + */ +float_t randomFloat(const float_t min, const float_t max);