This commit is contained in:
2025-06-10 21:26:00 -05:00
parent ac917901e4
commit d1b1ecf3ca
16 changed files with 458 additions and 73 deletions

View File

@ -10,29 +10,18 @@
#include "display/render.h"
#include "rpg/entity/entity.h"
#include "rpg/world/maps/testmap.h"
int32_t main(const int32_t argc, const char **argv) {
inputInit();
randomInit();
renderInit();
entity_t *ent;
ent = &ENTITIES[0];
entityInit(ent, ENTITY_TYPE_PLAYER);
mapSet(TEST_MAP);
while(1) {
inputUpdate();
ent = ENTITIES;
do {
if(ent->type == ENTITY_TYPE_NULL) {
ent++;
continue;
}
entityUpdate(ent++);
} while(ent < (ENTITIES + ENTITY_COUNT));
mapUpdate();
if(!renderUpdate()) break;
}

View File

@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
entity.c
player.c
npc.c
)

View File

@ -8,20 +8,25 @@
#include "assert/assert.h"
#include "util/memory.h"
#include "entity.h"
entity_t ENTITIES[ENTITY_COUNT];
#include "rpg/world/map.h"
#include "util/math.h"
entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
[ENTITY_TYPE_NULL] = { 0 },
[ENTITY_TYPE_PLAYER] = {
.init = playerInit,
.update = playerUpdate
.update = playerUpdate,
.interact = NULL,
},
[ENTITY_TYPE_NPC] = {
.init = npcInit,
.update = npcUpdate,
.interact = npcInteract,
}
};
void entityInit(entity_t *entity, const entitytype_t type) {
assertNotNull(entity, "Entity is NULL");
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
@ -60,42 +65,128 @@ void entityTurn(entity_t *entity, const entitydir_t dir) {
entity->dir = dir;
}
void entityWalk(entity_t *entity) {
assertNotNull(entity, "Entity cannot be NULL");
assertFalse(entityIsWalking(entity), "Entity is already walking");
void entityDirGetPosition(
const entitydir_t dir,
const uint8_t distance,
int8_t *outX,
int8_t *outY
) {
assertTrue(dir < ENTITY_DIR_COUNT, "Invalid entity direction");
assertNotNull(outX, "Output X cannot be NULL");
assertNotNull(outY, "Output Y cannot be NULL");
switch(entity->dir) {
switch(dir) {
case ENTITY_DIR_UP:
entity->y--;
entity->subY = ENTITY_MOVE_SUBPIXEL;
*outX = 0;
*outY = -distance;
break;
case ENTITY_DIR_DOWN:
entity->y++;
entity->subY = -ENTITY_MOVE_SUBPIXEL;
*outX = 0;
*outY = distance;
break;
case ENTITY_DIR_LEFT:
entity->x--;
entity->subX = ENTITY_MOVE_SUBPIXEL;
*outX = -distance;
*outY = 0;
break;
case ENTITY_DIR_RIGHT:
entity->x++;
entity->subX = -ENTITY_MOVE_SUBPIXEL;
*outX = distance;
*outY = 0;
break;
default:
assertUnreachable("Invalid entity direction");
}
}
entitydir_t entityGetLookDirection(
const entity_t *entity,
const uint8_t x,
const uint8_t y
) {
assertNotNull(entity, "Entity cannot be NULL");
assertTrue(x < MAP.width, "X coordinate out of bounds");
assertTrue(y < MAP.height, "Y coordinate out of bounds");
int8_t dX = x - entity->x;
int8_t dY = y - entity->y;
// More horizontal movement or more vertical movement?
if(mathAbsI8(dX) > mathAbsI8(dY)) {
// More horizontal movement
if(dX < 0) return ENTITY_DIR_LEFT;
return ENTITY_DIR_RIGHT;
}
if(dY < 0) {
return ENTITY_DIR_UP;
}
return ENTITY_DIR_DOWN;
}
void entityWalk(entity_t *entity) {
assertNotNull(entity, "Entity cannot be NULL");
assertFalse(entityIsWalking(entity), "Entity is already walking");
assertTrue(entityCanWalk(entity, entity->dir, NULL), "Entity cannot walk");
int8_t tX, tY;
entityDirGetPosition(entity->dir, 1, &tX, &tY);
entity->y += tY;
entity->x += tX;
entity->subX = ENTITY_MOVE_SUBPIXEL * -tX;
entity->subY = ENTITY_MOVE_SUBPIXEL * -tY;
}
bool_t entityIsWalking(const entity_t *entity) {
assertNotNull(entity, "Entity cannot be NULL");
return (entity->subX != 0 || entity->subY != 0);
}
entity_t * entityGetAt(const uint8_t x, const uint8_t y) {
entity_t *e = ENTITIES;
do {
if(e->type && e->x == x && e->y == y) return e;
e++;
} while(e < (ENTITIES + ENTITY_COUNT));
return NULL;
bool_t entityCanWalk(
const entity_t *entity,
const entitydir_t dir,
entity_t **entityInWay
) {
assertNotNull(entity, "Entity cannot be NULL");
assertTrue(dir < ENTITY_DIR_COUNT, "Invalid entity direction");
int8_t dX, dY;
entityDirGetPosition(dir, 1, &dX, &dY);
uint8_t tX = entity->x + dX;
if(tX < 0 || tX >= MAP.width) return false;
uint8_t tY = entity->y + dY;
if(tY < 0 || tY >= MAP.height) return false;
if(entityInWay == NULL) {
if(mapGetEntityAt(tX, tY) != NULL) return false;
} else {
*entityInWay = mapGetEntityAt(tX, tY);
if(*entityInWay != NULL) return false;
}
return true;
}
void entityPositionSet(entity_t *entity, const uint8_t x, const uint8_t y) {
assertNotNull(entity, "Entity cannot be NULL");
assertTrue(x < MAP.width, "X coordinate out of bounds");
assertTrue(y < MAP.height, "Y coordinate out of bounds");
entity->x = x;
entity->y = y;
entity->subX = 0;
entity->subY = 0;
}
bool_t entityInteract(entity_t *interacted, entity_t *player) {
assertNotNull(interacted, "Interacted entity cannot be NULL");
assertNotNull(player, "Player entity cannot be NULL");
assertTrue(interacted->type != ENTITY_TYPE_NULL, "Interacted entity invalid");
assertTrue(player->type == ENTITY_TYPE_PLAYER, "Entity is not a player");
if(ENTITY_CALLBACKS[interacted->type].interact != NULL) {
return ENTITY_CALLBACKS[interacted->type].interact(interacted, player);
}
return false;
}

View File

@ -7,6 +7,7 @@
#pragma once
#include "player.h"
#include "npc.h"
#define ENTITY_WIDTH 16
#define ENTITY_HEIGHT 16
@ -24,11 +25,14 @@ typedef enum {
ENTITY_DIR_EAST = ENTITY_DIR_RIGHT
} entitydir_t;
#define ENTITY_DIR_COUNT (ENTITY_DIR_RIGHT + 1)
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER = 1,
ENTITY_TYPE_NPC = 2,
} entitytype_t;
#define ENTITY_TYPE_COUNT (ENTITY_TYPE_PLAYER + 1)
#define ENTITY_TYPE_COUNT (ENTITY_TYPE_NPC + 1)
typedef struct _entity_t {
entitytype_t type;
@ -39,15 +43,14 @@ typedef struct _entity_t {
// Per type data
union {
player_t player;
npc_t npc;
};
} entity_t;
#define ENTITY_COUNT 16
extern entity_t ENTITIES[ENTITY_COUNT];
typedef struct {
void (*init)(entity_t *entity);
void (*update)(entity_t *entity);
bool_t (*interact)(entity_t *self, entity_t *player);
} entitycallbacks_t;
extern entitycallbacks_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
@ -74,6 +77,36 @@ void entityUpdate(entity_t *entity);
*/
void entityTurn(entity_t *entity, const entitydir_t dir);
/**
* Gets the position of a specific direction at a given distance.
*
* @param dir The direction to get the position in.
* @param distance The distance to move in that direction.
* @param outX Pointer to store the resulting x-coordinate.
* @param outY Pointer to store the resulting y-coordinate.
*/
void entityDirGetPosition(
const entitydir_t dir,
const uint8_t distance,
int8_t *outX,
int8_t *outY
);
/**
* Gets the look direction for a given entity to be looking at the specified
* coordinates.
*
* @param entity Pointer to the entity to get the look direction for.
* @param x The x-coordinate to look at.
* @param Y The y-coordinate to look at.
* @return The direction the entity should look towards.
*/
entitydir_t entityGetLookDirection(
const entity_t *entity,
const uint8_t x,
const uint8_t Y
);
/**
* Makes the entity walk in the current direction.
*
@ -90,10 +123,36 @@ void entityWalk(entity_t *entity);
bool_t entityIsWalking(const entity_t *entity);
/**
* Resets the entity at a given position.
* Checks if the entity can walk in a specific direction.
*
* @param x The x-coordinate of the entity.
* @param y The y-coordinate of the entity.
* @return Pointer to the entity at the specified position, or NULL.
* @param entity Pointer to the entity to check.
* @param dir The direction to check for walking capability.
* @param inWay Pointer to store any entity in the way, if applicable.
* @return true if the entity can walk in the specified direction, false otherwise.
*/
entity_t * entityGetAt(const uint8_t x, const uint8_t y);
bool_t entityCanWalk(
const entity_t *entity,
const entitydir_t dir,
entity_t **inWay
);
/**
* Sets the position of the entity.
*
* @param entity Pointer to the entity to set the position for.
* @param x The x-coordinate to set.
* @param y The y-coordinate to set.
*/
void entityPositionSet(entity_t *entity, const uint8_t x, const uint8_t y);
/**
* Handles interaction between an entity and a player.
*
* @param self Pointer to the entity that is being interacted with.
* @param player Pointer to the player entity interacting with the entity.
* @return true if interaction happened.
*/
bool_t entityInteract(
entity_t *self,
entity_t *player
);

31
src/dusk/rpg/entity/npc.c Normal file
View File

@ -0,0 +1,31 @@
/**
* 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"
void npcInit(entity_t *ent) {
assertNotNull(ent, "NPC entity is NULL");
assertTrue(ent->type == ENTITY_TYPE_NPC, "Entity is not an NPC");
}
void npcUpdate(entity_t *ent) {
assertNotNull(ent, "Entity is NULL");
assertTrue(ent->type == ENTITY_TYPE_NPC, "Entity is not an NPC");
}
bool_t npcInteract(entity_t *self, entity_t *player) {
assertNotNull(self, "NPC entity cannot be NULL");
assertNotNull(player, "Player entity cannot be NULL");
assertTrue(self->type == ENTITY_TYPE_NPC, "Entity is not an NPC");
assertTrue(player->type == ENTITY_TYPE_PLAYER, "Entity is not a player");
// Look at the player
entityTurn(self, entityGetLookDirection(self, player->x, player->y));
return true;
}

37
src/dusk/rpg/entity/npc.h Normal file
View File

@ -0,0 +1,37 @@
/**
* 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_t entity_t;
typedef struct {
uint8_t nothing; // Placeholder for NPC-specific data
} npc_t;
/**
* Initializes an NPC entity.
*
* @param ent Pointer to the NPC entity to initialize.
*/
void npcInit(entity_t *ent);
/**
* Updates an NPC entity.
*
* @param ent Entity to update.
*/
void npcUpdate(entity_t *ent);
/**
* Handles interaction between an NPC and a player.
*
* @param self Pointer to the NPC entity.
* @param player Pointer to the player entity interacting with the NPC.
*/
bool_t npcInteract(entity_t *self, entity_t *player);

View File

@ -8,6 +8,7 @@
#include "entity.h"
#include "assert/assert.h"
#include "input.h"
#include "rpg/world/map.h"
void playerInit(entity_t *player) {
assertNotNull(player, "Player entity is NULL");
@ -15,28 +16,43 @@ void playerInit(entity_t *player) {
}
void playerUpdate(entity_t *entity) {
entity_t *other;
assertNotNull(entity, "Entity is NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity is not a player");
if(!entityIsWalking(entity)) {
entitydir_t dir = 0xFF;
if(inputIsDown(INPUT_UP)) {
dir = ENTITY_DIR_UP;
} else if(inputIsDown(INPUT_DOWN)) {
dir = ENTITY_DIR_DOWN;
} else if(inputIsDown(INPUT_LEFT)) {
dir = ENTITY_DIR_LEFT;
} else if(inputIsDown(INPUT_RIGHT)) {
dir = ENTITY_DIR_RIGHT;
}
if(dir != 0xFF) {
if(dir != entity->dir) {
entityTurn(entity, dir);
} else {
entityWalk(entity);
}
}
// Handle movement
if(entityIsWalking(entity)) {
return;
}
entitydir_t dir = 0xFF;
if(inputIsDown(INPUT_UP)) {
dir = ENTITY_DIR_UP;
} else if(inputIsDown(INPUT_DOWN)) {
dir = ENTITY_DIR_DOWN;
} else if(inputIsDown(INPUT_LEFT)) {
dir = ENTITY_DIR_LEFT;
} else if(inputIsDown(INPUT_RIGHT)) {
dir = ENTITY_DIR_RIGHT;
}
if(dir != 0xFF) {
if(dir != entity->dir) {
entityTurn(entity, dir);
} else {
if(!entityCanWalk(entity, dir, &other)) return;
entityWalk(entity);
}
return;
}
// Handle interaction
if(inputWasPressed(INPUT_ACTION)) {
int8_t dX, dY;
entityDirGetPosition(entity->dir, 1, &dX, &dY);
other = mapGetEntityAt(entity->x + dX, entity->y + dY);
assertTrue(other != entity, "Player trying to interact with itself?");
if(other != NULL && entityInteract(other, entity)) return;
}
}

View File

@ -7,4 +7,7 @@
target_sources(${DUSK_TARGET_NAME}
PRIVATE
map.c
)
)
# Subdirs
add_subdirectory(maps)

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "map.h"
#include "util/memory.h"
#include "assert/assert.h"
map_t MAP;
void mapSet(const mapinfo_t mapInfo) {
assertTrue(mapInfo.init != NULL, "Map initialization function cannot be NULL");
mapInfo.init();
}
void mapInit(const uint8_t width, const uint8_t height) {
memoryZero(&MAP, sizeof(map_t));
MAP.width = width;
MAP.height = height;
}
void mapUpdate() {
entity_t *ent = MAP.entities;
do {
if(ent->type != ENTITY_TYPE_NULL) {
entityUpdate(ent);
}
ent++;
} while(ent < MAP.entities + MAP_ENTITY_COUNT);
}
entity_t * mapGetEntityAt(const uint8_t x, const uint8_t y) {
if(x >= MAP.width || y >= MAP.height) return NULL;
entity_t *ent = MAP.entities;
do {
if(ent->type != ENTITY_TYPE_NULL && ent->x == x && ent->y == y) return ent;
ent++;
} while(ent < MAP.entities + MAP_ENTITY_COUNT);
return NULL;
}

View File

@ -6,11 +6,54 @@
*/
#pragma once
#include "dusk.h"
#include "rpg/entity/entity.h"
#define MAP_ENTITY_COUNT 16
typedef struct {
uint8_t width;
uint8_t height;
entity_t entities[MAP_ENTITY_COUNT];
} map_t;
extern map_t MAP;
typedef struct {
const char *name;
void (*init)(void);
} mapinfo_t;
extern map_t MAP;
/**
* Sets the current map to the specified map information.
*
* This function initializes the map with the provided map information,
* including its name and initialization function.
*
* @param mapInfo Map information containing the name and initialization function.
*/
void mapSet(const mapinfo_t mapInfo);
/**
* Initializes the map with the given width and height.
*
* @param width Width of the map.
* @param height Height of the map.
*/
void mapInit(const uint8_t width, const uint8_t height);
/**
* Updates the map, processing all entities.
*
* This function should be called every frame to update the state of the map
* and its entities.
*/
void mapUpdate();
/**
* Gets the entity at the specified coordinates.
*
* @param x X coordinate of the entity.
* @param y Y coordinate of the entity.
* @return Pointer to the entity at the specified coordinates or NULL.
*/
entity_t * mapGetEntityAt(const uint8_t x, const uint8_t y);

View File

@ -0,0 +1,9 @@
# 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
)

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/entity/entity.h"
#include "rpg/world/map.h"
/**
* Initializes the test map.
*
* This function sets up a test map with predefined dimensions.
*/
void testMapInit() {
mapInit(10, 10);
entity_t *ent = MAP.entities;
entityInit(ent, ENTITY_TYPE_PLAYER);
ent++;
entityInit(ent, ENTITY_TYPE_NPC);
entityPositionSet(ent, 5, 5);
}
mapinfo_t TEST_MAP = {
.name = "Test Map",
.init = testMapInit
};

View File

@ -9,4 +9,5 @@ target_sources(${DUSK_TARGET_NAME}
memory.c
string.c
random.c
math.c
)

12
src/dusk/util/math.c Normal file
View File

@ -0,0 +1,12 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "math.h"
int8_t mathAbsI8(const int8_t val) {
return (val > 0) ? val : -val;
}

17
src/dusk/util/math.h Normal file
View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
/**
* Returns the absolute (ignoring sign) value of an 8-bit integer.
*
* @param val The 8-bit integer value to get the absolute value of.
* @return The absolute value of the input integer.
*/
int8_t mathAbsI8(const int8_t val);

View File

@ -8,7 +8,7 @@
#include "assert/assert.h"
#include "display/render.h"
#include "raylib.h"
#include "rpg/entity/entity.h"
#include "rpg/world/map.h"
const uint16_t RENDER_WIDTH = 480;
const uint16_t RENDER_HEIGHT = 270;
@ -29,7 +29,7 @@ bool_t renderUpdate() {
ClearBackground(RAYWHITE);
DrawText("Hello, Dusk!", 10, 10, 20, BLACK);
entity_t *ent = ENTITIES;
entity_t *ent = MAP.entities;
do {
if(ent->type == ENTITY_TYPE_NULL) {
ent++;
@ -83,7 +83,7 @@ bool_t renderUpdate() {
}
ent++;
} while(ent < (ENTITIES + ENTITY_COUNT));
} while(ent < (MAP.entities + MAP_ENTITY_COUNT));
EndDrawing();