Made a big mess of the codebase

This commit is contained in:
2025-08-28 07:14:13 -05:00
parent 30232d1275
commit af1329710d
44 changed files with 1363 additions and 388 deletions

View File

@@ -30,7 +30,6 @@ add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(ecs)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(input)

View File

@@ -8,67 +8,63 @@
#include "camera.h"
#include "display/display.h"
#include "assert/assert.h"
#include "scene/node.h"
#include "display/framebuffer/framebuffer.h"
camera_t CAMERA_DATA[ECS_ENTITY_COUNT_MAX] = { 0 };
ecscomponent_t CAMERA_COMPONENT = ecsComponentInit(
CAMERA_DATA,
((ecscomponentcallbacks_t){
.init = NULL,
.entityAdd = cameraEntityAdded
})
);
camera_t CAMERA_DATA[CAMERA_COUNT_MAX] = { 0 };
camera_t *CAMERA_MAIN = NULL;
ecsid_t CAMERA_MAIN = -1;
void cameraInit(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
void cameraEntityAdded(const ecsid_t id) {
if(CAMERA_MAIN == -1) CAMERA_MAIN = id;
camera->type = CAMERA_TYPE_PERSPECTIVE
;
glm_mat4_identity(camera->transform);
camera->perspective.fov = 45.0f;
camera_t *cam = cameraGet(id);
cam->type = CAMERA_TYPE_PERSPECTIVE;
cam->perspective.fov = glm_rad(90.0f);
cam->nearClip = 0.1f;
cam->farClip = 1000.0f;
camera->nearClip = 0.1f;
camera->farClip = 100.0f;
glm_look(
(vec3){ 3.0f, 3.0f, 3.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
camera->transform
);
}
void cameraPush(const ecsid_t id) {
assertTrue(cameraHas(id), "Not a camera component");
void cameraPush(camera_t *camera) {
assertNotNull(camera, "Not a camera component");
camera_t *cam = cameraGet(id);
mat4 projection;
mat4 projection, view;
nodeMatrixGet(id, view);
switch(cam->type) {
switch(camera->type) {
case CAMERA_TYPE_ORTHOGRAPHIC:
glm_ortho(
cam->orthographic.left,
cam->orthographic.right,
cam->orthographic.bottom,
cam->orthographic.top,
cam->nearClip,
cam->farClip,
camera->orthographic.left,
camera->orthographic.right,
camera->orthographic.bottom,
camera->orthographic.top,
camera->nearClip,
camera->farClip,
projection
);
break;
case CAMERA_TYPE_PERSPECTIVE:
glm_perspective(
cam->perspective.fov,
camera->perspective.fov,
(
(float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND) /
(float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
),
cam->nearClip,
cam->farClip,
camera->nearClip,
camera->farClip,
projection
);
}
#if DISPLAY_SDL2
mat4 pv;
glm_mat4_mul(projection, view, pv);
glm_mat4_mul(projection, camera->transform, pv);
glPushMatrix();
glMatrixMode(GL_PROJECTION);

View File

@@ -6,9 +6,11 @@
*/
#pragma once
#include "ecs/ecscomponent.h"
#include "dusk.h"
#include "display/color.h"
#define CAMERA_COUNT_MAX 4
typedef enum {
CAMERA_TYPE_PERSPECTIVE,
CAMERA_TYPE_ORTHOGRAPHIC
@@ -17,6 +19,8 @@ typedef enum {
typedef struct {
cameraprojectiontype_t type;
mat4 transform;
union {
struct {
float_t fov;
@@ -34,31 +38,22 @@ typedef struct {
float_t farClip;
} camera_t;
extern camera_t CAMERA_DATA[ECS_ENTITY_COUNT_MAX];
extern ecscomponent_t CAMERA_COMPONENT;
extern ecsid_t CAMERA_MAIN;
#define cameraAdd(id) ((camera_t*)ecsComponentDataAdd(&CAMERA_COMPONENT, id))
#define cameraGet(id) ((camera_t*)ecsComponentDataGet(&CAMERA_COMPONENT, id))
#define cameraHas(id) ecsComponentDataHas(&CAMERA_COMPONENT, id)
#define cameraRemove(id) ecsComponentDataRemove(&CAMERA_COMPONENT, id)
extern camera_t CAMERA_DATA[CAMERA_COUNT_MAX];
extern camera_t *CAMERA_MAIN;
/**
* Callback function called when a new entity is added to the camera component.
* Initializes the camera data for the entity.
*
* @param id The ID of the newly added entity.
* Initializes a camera to default values.
*/
void cameraEntityAdded(const ecsid_t id);
void cameraInit(camera_t *camera);
/**
* Pushes the camera's view matrix onto the matrix stack.
*
* @param id The ID of the camera entity to use.
*/
void cameraPush(const ecsid_t id);
void cameraPushMatrix(camera_t* camera);
/**
* Pops the camera's view matrix off the matrix stack.
*/
void cameraPop(void);
void cameraPopMatrix(void);

View File

@@ -8,7 +8,6 @@
#include "display/display.h"
#include "console/console.h"
#include "display/renderer.h"
#include "ecs/ecssystem.h"
#include "display/framebuffer/framebuffer.h"
#include "display/mesh/quad.h"
@@ -84,7 +83,7 @@ errorret_t displayUpdate(void) {
glViewport(0, 0, windowWidth, windowHeight);
#endif
rendererRender(CAMERA_MAIN);
// rendererRender(CAMERA_MAIN);
#if DISPLAY_SDL2
SDL_GL_SwapWindow(DISPLAY.window);

View File

@@ -8,5 +8,4 @@ target_sources(${DUSK_TARGET_NAME}
PRIVATE
mesh.c
quad.c
meshrenderer.c
)

View File

@@ -1,28 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "meshrenderer.h"
#include "time/time.h"
meshrenderer_t MESH_RENDERER_DATA[ECS_ENTITY_COUNT_MAX] = { 0 };
ecscomponent_t MESH_RENDERER_COMPONENT = ecsComponentInit(
MESH_RENDERER_DATA,
((ecscomponentcallbacks_t){
.init = NULL,
.entityAdd = NULL,
.entityRemove = NULL
})
);
void meshRendererDraw(const ecsid_t id) {
if(!meshRendererHas(id)) return;
meshrenderer_t *renderer = &MESH_RENDERER_DATA[id];
if(!renderer->mesh) return;
textureBind(renderer->texture);
meshDraw(renderer->mesh, 0, -1);
}

View File

@@ -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 "ecs/ecscomponent.h"
#include "display/mesh/mesh.h"
#include "display/texture/texture.h"
typedef struct {
mesh_t *mesh;
texture_t *texture;
} meshrenderer_t;
extern meshrenderer_t MESH_RENDERER_DATA[ECS_ENTITY_COUNT_MAX];
extern ecscomponent_t MESH_RENDERER_COMPONENT;
#define meshRendererAdd(id) \
((meshrenderer_t*)ecsComponentDataAdd(&MESH_RENDERER_COMPONENT, id))
#define meshRendererGet(id) \
((meshrenderer_t*)ecsComponentDataGet(&MESH_RENDERER_COMPONENT, id))
#define meshRendererHas(id) \
(ecsComponentDataHas(&MESH_RENDERER_COMPONENT, id))
#define meshRendererRemove(id) \
ecsComponentDataRemove(&MESH_RENDERER_COMPONENT, id)
#define meshRendererGetAll(out) \
ecsComponentGetAll(&MESH_RENDERER_COMPONENT, out)
/**
* Draw the mesh for the given entity.
*
* @param id The ID of the entity with the mesh renderer component.
*/
void meshRendererDraw(const ecsid_t id);

View File

@@ -6,29 +6,27 @@
*/
#include "renderer.h"
#include "display/mesh/meshrenderer.h"
#include "scene/node.h"
#include "display/framebuffer/framebuffer.h"
void rendererRender(const ecsid_t camera) {
if(camera == -1) return;
// void rendererRender(const ecsid_t camera) {
// if(camera == -1) return;
// Get the meshes.
uint32_t meshCount;
ecsid_t meshes[ECS_ENTITY_COUNT_MAX];
ecsid_t id;
meshCount = meshRendererGetAll(meshes);
// // Get the meshes.
// uint32_t meshCount;
// ecsid_t meshes[ECS_ENTITY_COUNT_MAX];
// ecsid_t id;
// meshCount = meshRendererGetAll(meshes);
frameBufferBind(NULL);
frameBufferClear(
FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
COLOR_CORNFLOWER_BLUE
);
cameraPush(camera);
for(uint32_t i = 0; i < meshCount; i++) {
id = meshes[i];
nodeMatrixPush(id);
meshRendererDraw(id);
}
cameraPop();
}
// frameBufferBind(NULL);
// frameBufferClear(
// FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH,
// COLOR_CORNFLOWER_BLUE
// );
// cameraPush(camera);
// for(uint32_t i = 0; i < meshCount; i++) {
// id = meshes[i];
// nodeMatrixPush(id);
// meshRendererDraw(id);
// }
// cameraPop();
// }

View File

@@ -13,4 +13,4 @@
*
* @param camera The ID of the camera entity to render from.
*/
void rendererRender(const ecsid_t camera);
// void rendererRender(const ecsid_t camera);

View File

@@ -10,8 +10,6 @@
#include "time/time.h"
#include "console/console.h"
#include "display/display.h"
#include "ecs/ecssystem.h"
#include "scene/node.h"
#include "asset/asset.h"
#include "scene/test/scenetest.h"
@@ -31,7 +29,6 @@ errorret_t engineInit(void) {
// Init systems. Order is important.
timeInit();
consoleInit();
ecsSystemInit();
errorChain(assetInit());
errorChain(displayInit());
@@ -52,7 +49,6 @@ errorret_t engineUpdate(void) {
}
errorret_t engineDispose(void) {
ecsSystemDispose();
errorChain(displayDispose());
assetDispose();
consoleDispose();

14
src/rpg/CMakeLists.txt Normal file
View File

@@ -0,0 +1,14 @@
# 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
)
# Subdirs
add_subdirectory(entity)
add_subdirectory(item)
add_subdirectory(world)

View File

@@ -0,0 +1,13 @@
# 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
direction.c
entity.c
player.c
npc.c
)

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "direction.h"
#include "assert/assert.h"
float_t directionToAngle(const direction_t dir) {
switch(dir) {
case DIRECTION_NORTH: return (M_PI_2);
case DIRECTION_SOUTH: return -(M_PI_2);
case DIRECTION_EAST: return 0;
case DIRECTION_WEST: return (M_PI);
default: return 0; // Should never happen
}
}
void directionGetCoordinates(
const direction_t dir,
int8_t *x, int8_t *y
) {
assertNotNull(x, "X coordinate pointer cannot be NULL");
assertNotNull(y, "Y coordinate pointer cannot be NULL");
switch(dir) {
case DIRECTION_NORTH:
*x = 0;
*y = -1;
break;
case DIRECTION_SOUTH:
*x = 0;
*y = 1;
break;
case DIRECTION_EAST:
*x = 1;
*y = 0;
break;
case DIRECTION_WEST:
*x = -1;
*y = 0;
break;
default:
assertUnreachable("Invalid direction");
break;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
DIRECTION_SOUTH = 0,
DIRECTION_EAST = 1,
DIRECTION_WEST = 2,
DIRECTION_NORTH = 3,
DIRECTION_UP = DIRECTION_NORTH,
DIRECTION_DOWN = DIRECTION_SOUTH,
DIRECTION_LEFT = DIRECTION_WEST,
DIRECTION_RIGHT = DIRECTION_EAST,
} direction_t;
/**
* Converts a direction to an angle in float_t format.
*
* @param dir The direction to convert.
* @return The angle corresponding to the direction.
*/
float_t directionToAngle(const direction_t dir);
/**
* Gets the relative coordinates for a given direction.
*
* @param dir The direction to get coordinates for.
* @param x Pointer to store the x coordinate.
* @param y Pointer to store the y coordinate.
*/
void directionGetCoordinates(
const direction_t dir,
int8_t *x, int8_t *y
);

123
src/rpg/entity/entity.c Normal file
View File

@@ -0,0 +1,123 @@
/**
* 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 "util/memory.h"
// #include "world/world.h"
// #include "world/tiledata.h"
#include "time.h"
entity_t ENTITIES[ENTITY_COUNT_MAX] = {0};
// entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
// {NULL}, // ENTITY_TYPE_NULL
// {
// .load = playerEntityLoad,
// .update = playerEntityUpdate,
// },
// {
// .load = npcLoad,
// .update = npcUpdate,
// .interact = npcInteract,
// },
// };
void entityInit(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
memoryZero(entity, sizeof(entity_t));
// entity->type = ENTITY_TYPE_NULL;
// entity->x = 0;
// entity->y = 0;
// entity->dir = DIRECTION_SOUTH;
// entity->id = 0;
// ENTITY_CALLBACKS[entity->type].load(entity, source);
}
void entityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
// assertNotNull(
// ENTITY_CALLBACKS[entity->type].update,
// "Entity type has no update callback"
// );
// ENTITY_CALLBACKS[entity->type].update(entity);
if(entity->subX > 0) {
entity->subX -= entity->moveSpeed;
} else if(entity->subX < 0) {
entity->subX += entity->moveSpeed;
}
if(entity->subY > 0) {
entity->subY -= entity->moveSpeed;
} else if(entity->subY < 0) {
entity->subY += entity->moveSpeed;
}
}
void entityMove(entity_t *entity, const uint8_t moveSpeed) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
assertFalse(
entityIsMoving(entity),
"Entity is already moving, cannot move again"
);
int8_t x = 0, y = 0;
directionGetCoordinates(entity->dir, &x, &y);
// entity in way?
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
if(ent != NULL) return;
entity->x += x;
entity->y += y;
// entity->subX = TILE_WIDTH_HEIGHT * -x;
// entity->subY = TILE_WIDTH_HEIGHT * -y;
entity->moveSpeed = moveSpeed;
}
void entityTurn(entity_t *entity, const direction_t dir) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
assertTrue(
dir >= DIRECTION_SOUTH && dir <= DIRECTION_NORTH, "Invalid direction"
);
assertFalse(
entityIsMoving(entity), "Entity is already moving, cannot turn"
);
entity->dir = dir;
}
bool_t entityIsMoving(const entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
return entity->subX != 0 || entity->subY != 0;
}
entity_t * entityGetAt(
const int32_t tileX,
const int32_t tileY
) {
entity_t *entity = ENTITIES;
do {
if(entity->type == ENTITY_TYPE_NULL) continue;
if(entity->x == tileX && entity->y == tileY) return entity;
} while((entity++) < &ENTITIES[ENTITY_COUNT_MAX - 1]);
return NULL;
}

99
src/rpg/entity/entity.h Normal file
View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "direction.h"
#include "player.h"
#include "npc.h"
#define ENTITY_COUNT_MAX 32
#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
#define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
#define ENTITY_PLAYER_INDEX 0
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER = 1,
ENTITY_TYPE_NPC = 2,
} entitytype_t;
#define ENTITY_TYPE_COUNT 3
typedef struct _entity_t {
uint32_t id;// Completely unique ID for this entity.
int32_t x, y;
int8_t subX, subY;
uint8_t moveSpeed;
entitytype_t type;
direction_t dir;
union {
npc_t npc;
playerentity_t player;
};
} entity_t;
// typedef struct {
// void (*load) (entity_t *entity, const entity_t *source);
// void (*update) (entity_t *entity);
// void (*interact)(entity_t *player, entity_t *self);
// } entitycallback_t;
extern entity_t ENTITIES[ENTITY_COUNT_MAX];
// extern entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
/**
* Loads an entity from the generated entity data.
*
* @param entity Pointer to the entity to initialize.
* @param source Pointer to the source entity data.
*/
void entityInit(entity_t *entity);
/**
* Updates the entity's state.
*
* @param entity Pointer to the entity to update.
*/
void entityUpdate(entity_t *entity);
/**
* Moves the entity by the specified x and y offsets.
*
* @param entity Pointer to the entity to move.
* @param moveSpeed The speed at which to move the entity.
*/
void entityMove(entity_t *entity, const uint8_t moveSpeed);
/**
* Turns the entity to face the specified direction.
*
* @param entity Pointer to the entity to turn.
* @param dir The direction to turn the entity to.
*/
void entityTurn(entity_t *entity, const direction_t dir);
/**
* Returns whether or not an entity is currently moving.
*
* @param entity Pointer to the entity to check.
* @return True if the entity is moving, false otherwise.
*/
bool_t entityIsMoving(const entity_t *entity);
/**
* Gets the entity at the specified tile coordinates.
*
* @param tileX The x coordinate of the tile to get the entity from.
* @param tileY The y coordinate of the tile to get the entity from.
* @return Pointer to the entity at the specified coordinates, or NULL if no
* entity exists there.
*/
entity_t *entityGetAt(
const int32_t tileX,
const int32_t tileY
);

45
src/rpg/entity/npc.c Normal file
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 "entity.h"
// #include "ui/uitextbox.h"
// #include "locale/language.h"
#include "assert/assert.h"
void npcLoad(entity_t *entity, const entity_t *source) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertNotNull(source, "Source entity pointer cannot be NULL");
assertTrue(source->type == ENTITY_TYPE_NPC, "Source entity type must be NPC");
entity->npc = source->npc;
}
void npcUpdate(entity_t *entity) {
}
void npcInteract(entity_t *player, entity_t *self) {
assertTrue(self->type == ENTITY_TYPE_NPC, "Entity must be of type NPC");
switch(self->npc.interactType) {
case NPC_INTERACT_TYPE_NONE:
break;
case NPC_INTERACT_TYPE_TEXT:
// uiTextboxSetText(languageGet(self->npc.text));
break;
case NPC_INTERACT_TYPE_CONVO:
break;
case NPC_INTERACT_TYPE_EVENT:
// eventSetActive(self->npc.eventData);
break;
default:
assertUnreachable("Unknown NPC interaction type");
}
}

44
src/rpg/entity/npc.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include "dusk.h"
typedef struct _entity_t entity_t;
typedef enum {
NPC_INTERACT_TYPE_NONE = 0,
NPC_INTERACT_TYPE_TEXT = 1,
NPC_INTERACT_TYPE_CONVO = 2,
NPC_INTERACT_TYPE_EVENT = 3,
} npcinteracttype_t;
typedef struct {
npcinteracttype_t interactType;
union {
const char_t* text;
// const eventdata_t *eventData;
};
} npc_t;
/**
* Initializes the NPC entity.
*
* @param entity The entity to initialize.
* @param source The source entity to copy data from.
*/
void npcLoad(entity_t *entity, const entity_t *source);
/**
* Updates the NPC entity.
*
* @param entity The entity to update.
*/
void npcUpdate(entity_t *entity);
/**
* Handles interaction between the player and the NPC.
*
* @param player The player entity interacting with the NPC.
* @param self The NPC entity being interacted with.
*/
void npcInteract(entity_t *player, entity_t *self);

86
src/rpg/entity/player.c Normal file
View File

@@ -0,0 +1,86 @@
/**
* 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 "input/input.h"
// #include "display/render.h"
// #include "world/world.h"
// #include "ui/uitextbox.h"
inventory_t PLAYER_INVENTORY;
void playerInit() {
entity_t *ent = &ENTITIES[ENTITY_PLAYER_INDEX];
entityInit(ent);
inventoryInit(&PLAYER_INVENTORY, INVENTORY_SIZE_MAX);
}
void playerEntityLoad(entity_t *entity, const entity_t *source) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertNotNull(source, "Source entity pointer cannot be NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
assertTrue(source->type == entity->type, "Source/Entity type mismatch");
}
void playerEntityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity pointer cannot be NULL");
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
// TODO: make this just a method somewhere.
// if(UI_TEXTBOX.visible) return;
if(entityIsMoving(entity)) return;
const uint8_t moveSpeed = inputIsDown(INPUT_BIND_CANCEL) ? PLAYER_SPEED_RUN : PLAYER_SPEED_WALK;
if(inputIsDown(INPUT_BIND_UP)) {
if(entity->dir != DIRECTION_NORTH) {
entityTurn(entity, DIRECTION_NORTH);
return;
}
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_DOWN)) {
if(entity->dir != DIRECTION_SOUTH) {
entityTurn(entity, DIRECTION_SOUTH);
return;
}
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_LEFT)) {
if(entity->dir != DIRECTION_WEST) {
entityTurn(entity, DIRECTION_WEST);
return;
}
entityMove(entity, moveSpeed);
return;
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
if(entity->dir != DIRECTION_EAST) {
entityTurn(entity, DIRECTION_EAST);
return;
}
entityMove(entity, moveSpeed);
return;
}
// Interact
if(inputPressed(INPUT_BIND_ACTION)) {
int8_t x, y;
directionGetCoordinates(entity->dir, &x, &y);
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
// if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) {
// assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
// ENTITY_CALLBACKS[ent->type].interact(entity, ent);
// }
}
}

43
src/rpg/entity/player.h Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "item/inventory.h"
#define PLAYER_SPEED_WALK 1
#define PLAYER_SPEED_RUN 2
typedef struct _entity_t entity_t;
typedef struct {
uint32_t nothing;
} playerentity_t;
#define PLAYER_ENTITY_ID (UINT32_MAX-1)
extern inventory_t PLAYER_INVENTORY;
/**
* Initializes the player and all player-related entities.
*/
void playerInit(void);
/**
* Loads the player entity.
*
* @param entity The entity to initialize.
* @param source The source entity to copy data from.
*/
void playerEntityLoad(entity_t *entity, const entity_t *source);
/**
* Updates the player entity.
*
* @param entity The entity to update.
*/
void playerEntityUpdate(entity_t *entity);

View File

@@ -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
inventory.c
)

68
src/rpg/item/inventory.c Normal file
View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "inventory.h"
#include "util/memory.h"
#include "assert/assert.h"
void inventoryInit(inventory_t *inventory, const uint8_t size) {
assertNotNull(inventory, "inventory must not be NULL");
assertTrue(size <= INVENTORY_SIZE_MAX, "size exceeding INVENTORY_SIZE_MAX");
assertTrue(size > 0, "size must be greater than 0");
memoryZero(inventory, sizeof(inventory_t));
inventory->size = size;
}
uint8_t inventoryItemIndexByType(
const inventory_t *inventory,
const itemtype_t type
) {
assertNotNull(inventory, "inventory must not be NULL");
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
uint8_t item = inventory->itemCount;
while(item--) {
if(inventory->items[item].type == type) return item;
}
return INVENTORY_SIZE_MAX;
}
uint8_t inventoryItemCount(
const inventory_t *inventory,
const itemtype_t type
) {
assertNotNull(inventory, "inventory must not be NULL");
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
const uint8_t index = inventoryItemIndexByType(inventory, type);
if(index == INVENTORY_SIZE_MAX) return 0;
return inventory->items[index].count;
}
void inventoryItemSet(
inventory_t *inventory,
const itemtype_t type,
const uint8_t count
) {
assertNotNull(inventory, "inventory must not be NULL");
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
assertTrue(count > 0, "count must be greater than 0");
const uint8_t index = inventoryItemIndexByType(inventory, type);
if(index == INVENTORY_SIZE_MAX) {
// Item does not exist, add it
assertTrue(inventory->itemCount < inventory->size, "inventory is full");
inventory->items[inventory->itemCount].type = type;
inventory->items[inventory->itemCount].count = count;
inventory->itemCount++;
} else {
// Item exists, update the count
inventory->items[index].count = count;
}
}

63
src/rpg/item/inventory.h Normal file
View File

@@ -0,0 +1,63 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "itemstack.h"
#define INVENTORY_SIZE_MAX UINT8_MAX
typedef struct {
itemstack_t items[INVENTORY_SIZE_MAX];
uint8_t itemCount;
uint8_t size;
} inventory_t;
/**
* Initializes an inventory with a specified size.
*
* @param inventory Pointer to the inventory to initialize.
* @param size The size of the inventory (maximum is INVENTORY_SIZE_MAX).
*/
void inventoryInit(inventory_t *inventory, const uint8_t size);
/**
* Finds the index of the item of a specified type in the inventory.
*
* @param inventory Pointer to the inventory to search.
* @param type The type of item to find.
* @return The index of the item, or INVENTORY_SIZE_MAX if not found.
*/
uint8_t inventoryItemIndexByType(
const inventory_t *inventory,
const itemtype_t type
);
/**
* Gets the count of items of a specified type in the inventory.
*
* @param inventory Pointer to the inventory to check.
* @param type The type of item to count.
* @return The count of items of the specified type.
*/
uint8_t inventoryItemCount(
const inventory_t *inventory,
const itemtype_t type
);
/**
* Sets the count of items of a specified type in the inventory.
* If the item does not exist, it will be added.
*
* @param inventory Pointer to the inventory to modify.
* @param type The type of item to set.
* @param count The count of items to set.
*/
void inventoryItemSet(
inventory_t *inventory,
const itemtype_t type,
const uint8_t count
);

14
src/rpg/item/itemstack.h Normal file
View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "itemtype.h"
typedef struct {
itemtype_t type;
uint8_t count;
} itemstack_t;

24
src/rpg/item/itemtype.h Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
ITEM_TYPE_NULL = 0,
// MEDICINE
ITEM_TYPE_POTION,
// INGREDIENTS
ITEM_TYPE_ONION,
ITEM_TYPE_SWEET_POTATO,
ITEM_TYPE_CARROT,
// COOKED FOOD
ITEM_TYPE_BAKED_SWEET_POTATO,
} itemtype_t;

View File

@@ -0,0 +1,11 @@
# 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
chunk.c
overworld.c
)

321
src/rpg/world/chunk.c Normal file
View File

@@ -0,0 +1,321 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "chunk.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "world/world.h"
void renderChunkUpdated(chunk_t *chunk);
chunkmap_t CHUNK_MAP;
void chunkMapInit() {
memoryZero(&CHUNK_MAP, sizeof(chunkmap_t));
// Load default chunks, YX order.
uint16_t i = 0;
chunk_t *chunk;
for(uint8_t y = 0; y < CHUNK_MAP_HEIGHT; y++) {
for(uint8_t x = 0; x < CHUNK_MAP_WIDTH; x++) {
assertTrue(i < CHUNK_MAP_COUNT, "Chunk index out of bounds");
chunk = CHUNK_MAP.chunks + i;
CHUNK_MAP.chunkOrder[i] = chunk;
chunkLoad(chunk, x, y);
assertTrue(
chunk->x == x && chunk->y == y,
"Chunk coordinates do not match expected values"
);
i++;
}
}
}
void chunkMapShift(const int16_t x, const int16_t y) {
if(x == 0 && y == 0) assertUnreachable("ChunkMapShift called with no shift");
chunk_t *newChunkOrder[CHUNK_MAP_COUNT];
chunk_t *unloadedChunks[CHUNK_MAP_COUNT];
chunk_t *chunk;
uint8_t i, j;
uint16_t
/** New Map Coordinates */
newX, newY,
newChunkX, newChunkY
;
// Calculate the new map coordinates
newX = CHUNK_MAP.topLeftX + x;
newY = CHUNK_MAP.topLeftY + y;
// Zero the new chunk order
memoryZero(newChunkOrder, sizeof(newChunkOrder));
// For each chunk...
j = 0;
chunk = CHUNK_MAP.chunks;
do {
// Is this chunk still going to be within the map bounds?
if(
chunk->x < newX || chunk->y < newY ||
chunk->x >= newX + CHUNK_MAP_WIDTH ||
chunk->y >= newY + CHUNK_MAP_HEIGHT
) {
// No, it's not, let's unload it and make it available for reuse.
chunkUnload(chunk);
assertTrue(
j < CHUNK_MAP_COUNT,
"Unloaded chunk index out of bounds"
);
unloadedChunks[j++] = chunk;
chunk++;
continue;
}
// Yes it is still valid, determine the new index that it will be at
i = (chunk->y - newY) * CHUNK_MAP_WIDTH + (chunk->x - newX);
assertTrue(
i < CHUNK_MAP_COUNT,
"Chunk index out of bounds after shifting"
);
assertNull(
newChunkOrder[i],
"New chunk order index is already occupied"
);
// Set the new chunk order
newChunkOrder[i] = chunk;
chunk++;
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
// Now check the new chunk order list for missing chunks.
i = 0;
do {
assertTrue(
i < CHUNK_MAP_COUNT,
"New chunk order index out of bounds after shifting"
);
// Is this chunk loaded still?
chunk = newChunkOrder[i];
if(chunk != NULL) {
i++;
continue;
}
// Determine the new chunk coordinates.
newChunkX = i % CHUNK_MAP_WIDTH + newX;
newChunkY = i / CHUNK_MAP_WIDTH + newY;
assertTrue(
newChunkX >= newX && newChunkX < newX + CHUNK_MAP_WIDTH,
"New chunk X coordinate out of bounds after shifting"
);
assertTrue(
newChunkY >= newY && newChunkY < newY + CHUNK_MAP_HEIGHT,
"New chunk Y coordinate out of bounds after shifting"
);
// Pop a chunk from the unloaded chunks list.
assertTrue(j > 0, "No unloaded chunks available to reuse");
chunk = unloadedChunks[--j];
assertNotNull(chunk, "Unloaded chunk pointer is null");
// Load the chunk at the new coordinates.
chunkLoad(chunk, newChunkX, newChunkY);
assertTrue(
chunk->x == newChunkX && chunk->y == newChunkY,
"Chunk coordinates do not match expected values after shifting"
);
// Set it in order.
newChunkOrder[i] = chunk;
i++;
} while(i < CHUNK_MAP_COUNT);
// Update Absolutes.
CHUNK_MAP.topLeftX = newX;
CHUNK_MAP.topLeftY = newY;
// Update the chunk order.
memoryCopy(
CHUNK_MAP.chunkOrder,
newChunkOrder,
sizeof(CHUNK_MAP.chunkOrder)
);
}
void chunkMapSetPosition(const uint16_t x, const uint16_t y) {
if(x == CHUNK_MAP.topLeftX && y == CHUNK_MAP.topLeftY) {
return;
}
int16_t shiftX = x - CHUNK_MAP.topLeftX;
int16_t shiftY = y - CHUNK_MAP.topLeftY;
// Are we shifting the entire map?
if(
shiftX >= CHUNK_MAP_WIDTH || shiftX < -CHUNK_MAP_WIDTH ||
shiftY >= CHUNK_MAP_HEIGHT || shiftY < -CHUNK_MAP_HEIGHT
) {
printf("Shifting chunk map to new position (%u, %u)\n", x, y);
}
// Shift the chunk map by the specified offsets.
chunkMapShift(shiftX, shiftY);
}
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY) {
assertTrue(
chunkX < WORLD_WIDTH && chunkY < WORLD_HEIGHT,
"Chunk coordinates out of bounds"
);
chunk_t *chunk = CHUNK_MAP.chunks;
do {
if(chunk->x == chunkX && chunk->y == chunkY) return chunk;
chunk++;
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
return NULL;
}
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
assertNotNull(chunk, "Chunk pointer is null");
// Zero out the chunk data.
memoryZero(chunk, sizeof(chunk_t));
// Set the chunk coordinates.
chunk->x = x;
chunk->y = y;
// Only load data if the chunk is within bounds.
if(x >= WORLD_WIDTH || y >= WORLD_HEIGHT) {
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
return;
}
// Is chunk data defined?
const chunkdata_t *chunkData = WORLD_CHUNKS[y * WORLD_WIDTH + x];
if(chunkData == NULL) {
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
return;
}
// Load tile data into chunk
// printf("Loading chunk at (%u, %u)\n", x, y);
memoryCopy(
chunk->tilesBase,
chunkData->layerBase,
sizeof(chunk->tilesBase)
);
memoryCopy(
chunk->tilesBaseOverlay,
chunkData->layerBaseOverlay,
sizeof(chunk->tilesBaseOverlay)
);
// Load chunk entities
const entity_t *data;
entity_t *entity;
data = chunkData->entities;
while(data < chunkData->entities + CHUNK_ENTITY_COUNT_MAX) {
if(data->type == ENTITY_TYPE_NULL) break;
// Store that this chunk owns this entity ID.
chunk->entityIDs[chunk->entityCount++] = data->id;
// Check entity isn't loaded (still).
entity = ENTITIES;
do {
if(entity->type != ENTITY_TYPE_NULL && entity->id == data->id) break;
entity++;
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
if(entity != ENTITIES + ENTITY_COUNT_MAX) {
// Entity is already loaded, skip it.
printf("Entity ID %u already loaded, skipping...\n", data->id);
data++;
continue;
}
// Find an empty entity slot.
entity = ENTITIES;
while(true) {
assertTrue(
entity < ENTITIES + ENTITY_COUNT_MAX,
"Out of available entities"
);
if(entity->type == ENTITY_TYPE_NULL) break;
entity++;
};
// Load this entity.
entityLoad(entity, data);
data++;
}
// Allow the rendering platform to know this chunk is loaded.
renderChunkUpdated(chunk);
}
void chunkUnload(chunk_t *chunk) {
uint8_t i;
entity_t *entity;
uint32_t id;
assertNotNull(chunk, "Chunk pointer is null");
// Iterate over each entity this chunk owns.
i = 0;
while(i < chunk->entityCount) {
id = chunk->entityIDs[i++];
// Now, do we need to unload this entity?
bool_t shouldUnload = false;
// Now, find the entity loaded with this ID. It should be impossible for
// this entity to be unloaded (but may change in future).
entity = ENTITIES;
do {
if(entity->type != ENTITY_TYPE_NULL && entity->id == id) break;
entity++;
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
assertTrue(
entity < ENTITIES + ENTITY_COUNT_MAX,
"Entity ID not found in ENTITIES array, cannot unload"
);
// If the entity is still within our chunk bounds, it's getting unloaded
if(
floorf(entity->x) >= chunk->x * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
ceilf(entity->x) < (chunk->x + 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
floorf(entity->y) >= chunk->y * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT &&
ceilf(entity->y) < (chunk->y + 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT
) {
shouldUnload = true;
} else {
assertUnreachable(
"Entity has left its chunk bounds, we should not be unloading it but "
"I have yet to implement that properly. It will need to self-manage "
"its own unloading somehow, and also not be in a null chunk "
"when it does so."
);
}
// This entity is still in use, leave it loaded.
if(!shouldUnload) continue;
// NULL the entity type, effectively unloading it.
entity->type = ENTITY_TYPE_NULL;
}
}

84
src/rpg/world/chunk.h Normal file
View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "tile.h"
#include "display/render.h"
#define CHUNK_WIDTH 8
#define CHUNK_HEIGHT 8
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
#define CHUNK_ENTITY_COUNT_MAX 8
#define CHUNK_MAP_WIDTH (((RENDER_WIDTH / TILE_WIDTH_HEIGHT)/CHUNK_WIDTH)+2)
#define CHUNK_MAP_HEIGHT (((RENDER_HEIGHT / TILE_WIDTH_HEIGHT)/CHUNK_HEIGHT)+2)
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
typedef struct {
uint16_t x, y;
tile_t tilesBase[CHUNK_TILE_COUNT];
tile_t tilesBaseOverlay[CHUNK_TILE_COUNT];
uint32_t entityIDs[CHUNK_ENTITY_COUNT_MAX];
uint8_t entityCount;
} chunk_t;
typedef struct {
chunk_t chunks[CHUNK_MAP_COUNT];
chunk_t *chunkOrder[CHUNK_MAP_COUNT];
uint16_t topLeftX;
uint16_t topLeftY;
} chunkmap_t;
extern chunkmap_t CHUNK_MAP;
/**
* Initializes the chunk map.
*/
void chunkMapInit();
/**
* Shifts the chunk map by the specified x and y offsets.
*
* @param x The x offset to shift the chunk map.
* @param y The y offset to shift the chunk map.
*/
void chunkMapShift(const int16_t x, const int16_t y);
/**
* Sets the position of the chunk map to the specified coordinates.
*
* @param x The x coordinate of the top-left chunk.
* @param y The y coordinate of the top-left chunk.
*/
void chunkMapSetPosition(const uint16_t x, const uint16_t y);
/**
* Gets the chunk at the specified chunk coordinates.
*
* @param chunkX The x coordinate of the chunk.
* @param chunkY The y coordinate of the chunk.
* @return A pointer to the chunk at the specified chunk coordinates, or NULL if
* no chunk exists at those coordinates.
*/
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY);
/**
* Loads a chunk at the specified coordinates.
*
* @param chunk The chunk to load.
* @param x The x coordinate of the chunk.
* @param y The y coordinate of the chunk.
*/
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y);
/**
* Unloads a chunk (that is currently loaded).
*
* @param chunk The chunk to unload.
*/
void chunkUnload(chunk_t *chunk);

16
src/rpg/world/chunkdata.h Normal file
View File

@@ -0,0 +1,16 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "chunk.h"
#include "entity/entity.h"
typedef struct {
uint8_t layerBase[CHUNK_TILE_COUNT];
uint8_t layerBaseOverlay[CHUNK_TILE_COUNT];
entity_t entities[CHUNK_ENTITY_COUNT_MAX];
} chunkdata_t;

72
src/rpg/world/overworld.c Normal file
View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "overworld.h"
#include "chunk.h"
#include "display/render.h"
#include "assert/assert.h"
#include "entity/entity.h"
uint32_t OVERWORLD_CAMERA_X;
uint32_t OVERWORLD_CAMERA_Y;
overworldcameratype_t OVERWORLD_CAMERA_TYPE;
void overworldInit(void) {
playerInit();
chunkMapInit();
OVERWORLD_CAMERA_X = 0;
OVERWORLD_CAMERA_Y = 0;
OVERWORLD_CAMERA_TYPE = OVERWORLD_CAMERA_TYPE_CENTERED_POSITION;
}
void overworldUpdate() {
entity_t *entity;
assertTrue(
OVERWORLD_CAMERA_X < OVERWORLD_CAMERA_LIMIT_X,
"Camera position limit (just because I haven't tested properly)"
);
assertTrue(
OVERWORLD_CAMERA_Y < OVERWORLD_CAMERA_LIMIT_Y,
"Camera position limit (just because I haven't tested properly)"
);
entity = ENTITIES;
do {
entityUpdate(entity++);
} while(entity->type != ENTITY_TYPE_NULL);
// Testing, follow player
entity = &ENTITIES[0]; // Player entity
assertTrue(
entity->type == ENTITY_TYPE_PLAYER,
"First entity must be player"
);
OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX;
OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY;
uint16_t x, y;
uint16_t halfWidth, halfHeight;
halfWidth = ((CHUNK_MAP_WIDTH - 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT) / 2;
halfHeight = ((CHUNK_MAP_HEIGHT - 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT) / 2;
// Calculate the chunk map position based on the camera position.
if(OVERWORLD_CAMERA_X < halfWidth) {
x = 0;
} else {
x = (OVERWORLD_CAMERA_X - halfWidth) / (CHUNK_WIDTH*TILE_WIDTH_HEIGHT);
}
if(OVERWORLD_CAMERA_Y < halfHeight) {
y = 0;
} else {
y = (OVERWORLD_CAMERA_Y - halfHeight) / (CHUNK_HEIGHT*TILE_WIDTH_HEIGHT);
}
chunkMapSetPosition(x, y);
}

30
src/rpg/world/overworld.h Normal file
View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef enum {
OVERWORLD_CAMERA_TYPE_CENTERED_POSITION,
} overworldcameratype_t;
extern uint32_t OVERWORLD_CAMERA_X;
extern uint32_t OVERWORLD_CAMERA_Y;
extern overworldcameratype_t OVERWORLD_CAMERA_TYPE;
#define OVERWORLD_CAMERA_LIMIT_X (UINT32_MAX / 4)
#define OVERWORLD_CAMERA_LIMIT_Y (UINT32_MAX / 4)
/**
* Initializes the overworld.
*/
void overworldInit(void);
/**
* Updates the overworld.
*/
void overworldUpdate(void);

26
src/rpg/world/tile.h Normal file
View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#define TILE_WIDTH_HEIGHT 16
typedef uint8_t tile_t;
typedef enum {
TILE_SOLID_NONE = 0,
TILE_SOLID_FULL = 1,
TILE_SOLID_TRIANGLE_TOP_RIGHT = 2,
TILE_SOLID_TRIANGLE_TOP_LEFT = 3,
TILE_SOLID_TRIANGLE_BOTTOM_RIGHT = 4,
TILE_SOLID_TRIANGLE_BOTTOM_LEFT = 5,
} tilesolidtype_t;
typedef struct {
tilesolidtype_t solidType;
} tilemetadata_t;

View File

@@ -6,7 +6,6 @@
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
node.c
)
# Subdirs

View File

@@ -1,129 +0,0 @@
// Copyright (c) 2025 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "node.h"
#include "util/memory.h"
#include "assert/assert.h"
#include "display/display.h"
node_t NODE_DATA[ECS_ENTITY_COUNT_MAX] = { 0 };
ecscomponent_t NODE_COMPONENT = ecsComponentInit(
NODE_DATA,
((ecscomponentcallbacks_t){
.init = nodeInit,
.entityAdd = nodeEntityAdded,
.entityRemove = nodeEntityRemoved
})
);
void nodeInit(void) {
}
void nodeEntityAdded(const ecsid_t id) {
glm_mat4_identity(NODE_DATA[id].transform);
}
void nodeEntityRemoved(const ecsid_t id) {
}
void nodeMatrixGet(const ecsid_t id, mat4 dest) {
node_t *node;
if(nodeHas(id)) {
node = &NODE_DATA[id];
} else {
node = nodeAdd(id);
}
glm_mat4_copy(node->transform, dest);
}
void nodeMatrixSet(const ecsid_t id, mat4 in) {
node_t *node;
if(nodeHas(id)) {
node = &NODE_DATA[id];
} else {
node = nodeAdd(id);
}
glm_mat4_copy(in, node->transform);
// Extract position, scale, rotation from the matrix.
node->position[0] = in[3][0];
node->position[1] = in[3][1];
node->position[2] = in[3][2];
node->scale[0] = glm_vec3_norm((vec3){ in[0][0], in[0][1], in[0][2] });
node->scale[1] = glm_vec3_norm((vec3){ in[1][0], in[1][1], in[1][2] });
node->scale[2] = glm_vec3_norm((vec3){ in[2][0], in[2][1], in[2][2] });
// Remove scale from the matrix to extract rotation.
if(node->scale[0] != 0.0f) {
in[0][0] /= node->scale[0];
in[0][1] /= node->scale[0];
in[0][2] /= node->scale[0];
glm_vec3_copy(in[0], node->rotation);
glm_vec3_copy(in[1], node->rotation);
glm_vec3_copy(in[2], node->rotation);
node->rotation[1] = asinf(-in[0][2]);
if (cosf(node->rotation[1]) != 0.0f) {
node->rotation[0] = atan2f(in[1][2], in[2][2]);
node->rotation[2] = atan2f(in[0][1], in[0][0]);
} else {
node->rotation[0] = 0.0f;
node->rotation[2] = atan2f(-in[1][0], in[1][1]);
}
} else {
node->rotation[0] = 0.0f;
node->rotation[1] = 0.0f;
node->rotation[2] = 0.0f;
}
}
void nodePositionGet(const ecsid_t id, vec3 out) {
node_t *node;
if(nodeHas(id)) {
node = &NODE_DATA[id];
} else {
node = nodeAdd(id);
}
glm_vec3_copy(node->position, out);
}
void nodeMatrixUpdate(const ecsid_t id) {
node_t *node;
if(nodeHas(id)) {
node = &NODE_DATA[id];
} else {
node = nodeAdd(id);
}
glm_mat4_identity(node->transform);
mat4 rot;
glm_euler(node->rotation, rot);
glm_mat4_mul(node->transform, rot, node->transform);
// glm_scale(node->transform, node->scale);
}
void nodeMatrixPush(const ecsid_t id) {
assertTrue(nodeHas(id), "Not a node component");
node_t *node = nodeGet(id);
#if DISPLAY_SDL2
glPushMatrix();
glMultMatrixf((const GLfloat*)node->transform);
#endif
}
void nodeMatrixPop(void) {
#if DISPLAY_SDL2
glPopMatrix();
#endif
}

View File

@@ -1,83 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "ecs/ecscomponent.h"
#define SCENE_ITEM_CHILD_MAX 16
typedef struct {
mat4 transform;
vec3 position;
vec3 scale;
vec3 rotation; // Euler angles in radians
} node_t;
extern node_t NODE_DATA[ECS_ENTITY_COUNT_MAX];
extern ecscomponent_t NODE_COMPONENT;
#define nodeHas(id) ecsComponentDataHas(&NODE_COMPONENT, id)
#define nodeGet(id) \
((node_t*)ecsComponentDataGet(&NODE_COMPONENT, id))
#define nodeAdd(id) \
((node_t*)ecsComponentDataAdd(&NODE_COMPONENT, id))
#define nodeRemove(id) ecsComponentDataRemove(&NODE_COMPONENT, id)
/**
* Initialize the node component.
*/
void nodeInit(void);
/**
* Callback for when an entity is added to the ECS.
*
* @param id The ID of the entity being added.
*/
void nodeEntityAdded(const ecsid_t id);
/**
* Callback for when an entity is removed from the ECS.
*
* @param id The ID of the entity being removed.
*/
void nodeEntityRemoved(const ecsid_t id);
/**
* Get the local transformation matrix of a node.
*
* @param id The ID of the node.
* @param out Pointer to a mat4 where the local matrix will be stored.
*/
void nodeMatrixGet(const ecsid_t id, mat4 out);
/**
* Set the local transformation matrix of a node.
*
* @param id The ID of the node.
* @param in Pointer to a mat4 containing the new local matrix.
*/
void nodeMatrixSet(const ecsid_t id, mat4 in);
/**
* Get the local position of a node.
*
* @param id The ID of the node.
*/
void nodeMatrixUpdate(const ecsid_t id);
/**
* Push the node's transformation matrix onto the OpenGL matrix stack.
*
* @param id The ID of the node.
*/
void nodeMatrixPush(const ecsid_t id);
/**
* Pop the last transformation matrix from the OpenGL matrix stack.
*/
void nodeMatrixPop(void);

View File

@@ -6,38 +6,10 @@
*/
#include "scenetest.h"
#include "scene/node.h"
#include "display/camera.h"
#include "display/mesh/meshrenderer.h"
#include "display/mesh/quad.h"
texture_t test;
void sceneTestAdd(void) {
// Initialize the entity with a camera component
ecsid_t camera = ecsEntityAdd();
node_t *node = nodeAdd(camera);
camera_t *camData = cameraAdd(camera);
mat4 lookAt;
glm_lookat(
(vec3){ 3.0f, 3.0f, 3.0f },
(vec3){ 0.0f, 0.0f, 0.0f },
(vec3){ 0.0f, 1.0f, 0.0f },
lookAt
);
nodeMatrixSet(camera, lookAt);
// color4b_t pixels[4] = {
// COLOR_RED, COLOR_GREEN,
// COLOR_BLUE, COLOR_WHITE
// };
// textureInit(&test, 2, 2, TEXTURE_FORMAT_RGBA, pixels);
// Test cube
ecsid_t cube = ecsEntityAdd();
node = nodeAdd(cube);
meshrenderer_t *renderer = meshRendererAdd(cube);
renderer->mesh = &QUAD_MESH_SIMPLE;
renderer->texture = &test;
}

View File

@@ -6,7 +6,6 @@
*/
#pragma once
#include "ecs/ecssystem.h"
#include "display/texture/texture.h"
extern texture_t test;