diff --git a/src/ecs/CMakeLists.txt b/archive/dusk/ecs/CMakeLists.txt similarity index 100% rename from src/ecs/CMakeLists.txt rename to archive/dusk/ecs/CMakeLists.txt diff --git a/src/ecs/ecs.h b/archive/dusk/ecs/ecs.h similarity index 100% rename from src/ecs/ecs.h rename to archive/dusk/ecs/ecs.h diff --git a/src/ecs/ecscomponent.c b/archive/dusk/ecs/ecscomponent.c similarity index 100% rename from src/ecs/ecscomponent.c rename to archive/dusk/ecs/ecscomponent.c diff --git a/src/ecs/ecscomponent.h b/archive/dusk/ecs/ecscomponent.h similarity index 100% rename from src/ecs/ecscomponent.h rename to archive/dusk/ecs/ecscomponent.h diff --git a/src/ecs/ecsentity.h b/archive/dusk/ecs/ecsentity.h similarity index 100% rename from src/ecs/ecsentity.h rename to archive/dusk/ecs/ecsentity.h diff --git a/src/ecs/ecssystem.c b/archive/dusk/ecs/ecssystem.c similarity index 100% rename from src/ecs/ecssystem.c rename to archive/dusk/ecs/ecssystem.c diff --git a/src/ecs/ecssystem.h b/archive/dusk/ecs/ecssystem.h similarity index 100% rename from src/ecs/ecssystem.h rename to archive/dusk/ecs/ecssystem.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 07ee834..b594b2a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/display/camera.c b/src/display/camera.c index 79622fd..ee291de 100644 --- a/src/display/camera.c +++ b/src/display/camera.c @@ -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); diff --git a/src/display/camera.h b/src/display/camera.h index 1317ffa..a3f8f10 100644 --- a/src/display/camera.h +++ b/src/display/camera.h @@ -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); \ No newline at end of file +void cameraPopMatrix(void); \ No newline at end of file diff --git a/src/display/display.c b/src/display/display.c index 4cf3f7e..bcf28ca 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -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); diff --git a/src/display/mesh/CMakeLists.txt b/src/display/mesh/CMakeLists.txt index 45708f9..9befe9a 100644 --- a/src/display/mesh/CMakeLists.txt +++ b/src/display/mesh/CMakeLists.txt @@ -8,5 +8,4 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE mesh.c quad.c - meshrenderer.c ) \ No newline at end of file diff --git a/src/display/mesh/meshrenderer.c b/src/display/mesh/meshrenderer.c deleted file mode 100644 index dc11960..0000000 --- a/src/display/mesh/meshrenderer.c +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/src/display/mesh/meshrenderer.h b/src/display/mesh/meshrenderer.h deleted file mode 100644 index 0f35d1e..0000000 --- a/src/display/mesh/meshrenderer.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 "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); \ No newline at end of file diff --git a/src/display/renderer.c b/src/display/renderer.c index 8242d2d..0eb46fe 100644 --- a/src/display/renderer.c +++ b/src/display/renderer.c @@ -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(); -} \ No newline at end of file +// 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(); +// } \ No newline at end of file diff --git a/src/display/renderer.h b/src/display/renderer.h index 05a6cf3..50b963c 100644 --- a/src/display/renderer.h +++ b/src/display/renderer.h @@ -13,4 +13,4 @@ * * @param camera The ID of the camera entity to render from. */ -void rendererRender(const ecsid_t camera); \ No newline at end of file +// void rendererRender(const ecsid_t camera); \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c index b63c9e0..502fae8 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -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(); diff --git a/src/rpg/CMakeLists.txt b/src/rpg/CMakeLists.txt new file mode 100644 index 0000000..325ce9f --- /dev/null +++ b/src/rpg/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/src/rpg/entity/CMakeLists.txt b/src/rpg/entity/CMakeLists.txt new file mode 100644 index 0000000..fe59ea9 --- /dev/null +++ b/src/rpg/entity/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/rpg/entity/direction.c b/src/rpg/entity/direction.c new file mode 100644 index 0000000..e4f0e5e --- /dev/null +++ b/src/rpg/entity/direction.c @@ -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; + } +} \ No newline at end of file diff --git a/src/rpg/entity/direction.h b/src/rpg/entity/direction.h new file mode 100644 index 0000000..65d6f2e --- /dev/null +++ b/src/rpg/entity/direction.h @@ -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 +); \ No newline at end of file diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c new file mode 100644 index 0000000..c8f6848 --- /dev/null +++ b/src/rpg/entity/entity.c @@ -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; +} diff --git a/src/rpg/entity/entity.h b/src/rpg/entity/entity.h new file mode 100644 index 0000000..1744af8 --- /dev/null +++ b/src/rpg/entity/entity.h @@ -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 +); \ No newline at end of file diff --git a/src/rpg/entity/npc.c b/src/rpg/entity/npc.c new file mode 100644 index 0000000..cd94ac9 --- /dev/null +++ b/src/rpg/entity/npc.c @@ -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"); + } +} \ No newline at end of file diff --git a/src/rpg/entity/npc.h b/src/rpg/entity/npc.h new file mode 100644 index 0000000..3ed8136 --- /dev/null +++ b/src/rpg/entity/npc.h @@ -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); \ No newline at end of file diff --git a/src/rpg/entity/player.c b/src/rpg/entity/player.c new file mode 100644 index 0000000..5c23adb --- /dev/null +++ b/src/rpg/entity/player.c @@ -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); + // } + } +} \ No newline at end of file diff --git a/src/rpg/entity/player.h b/src/rpg/entity/player.h new file mode 100644 index 0000000..0780cbb --- /dev/null +++ b/src/rpg/entity/player.h @@ -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); \ No newline at end of file diff --git a/src/rpg/item/CMakeLists.txt b/src/rpg/item/CMakeLists.txt new file mode 100644 index 0000000..19db837 --- /dev/null +++ b/src/rpg/item/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + inventory.c +) \ No newline at end of file diff --git a/src/rpg/item/inventory.c b/src/rpg/item/inventory.c new file mode 100644 index 0000000..d123cf2 --- /dev/null +++ b/src/rpg/item/inventory.c @@ -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; + } +} \ No newline at end of file diff --git a/src/rpg/item/inventory.h b/src/rpg/item/inventory.h new file mode 100644 index 0000000..3515b96 --- /dev/null +++ b/src/rpg/item/inventory.h @@ -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 +); \ No newline at end of file diff --git a/src/rpg/item/itemstack.h b/src/rpg/item/itemstack.h new file mode 100644 index 0000000..b691d5d --- /dev/null +++ b/src/rpg/item/itemstack.h @@ -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; \ No newline at end of file diff --git a/src/rpg/item/itemtype.h b/src/rpg/item/itemtype.h new file mode 100644 index 0000000..3f07403 --- /dev/null +++ b/src/rpg/item/itemtype.h @@ -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; \ No newline at end of file diff --git a/src/rpg/world/CMakeLists.txt b/src/rpg/world/CMakeLists.txt new file mode 100644 index 0000000..446cdbf --- /dev/null +++ b/src/rpg/world/CMakeLists.txt @@ -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 +) \ 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..9bd2fbd --- /dev/null +++ b/src/rpg/world/chunk.c @@ -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; + } +} \ No newline at end of file diff --git a/src/rpg/world/chunk.h b/src/rpg/world/chunk.h new file mode 100644 index 0000000..c2bcf36 --- /dev/null +++ b/src/rpg/world/chunk.h @@ -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); \ No newline at end of file diff --git a/src/rpg/world/chunkdata.h b/src/rpg/world/chunkdata.h new file mode 100644 index 0000000..ecd2c31 --- /dev/null +++ b/src/rpg/world/chunkdata.h @@ -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; \ No newline at end of file diff --git a/src/rpg/world/overworld.c b/src/rpg/world/overworld.c new file mode 100644 index 0000000..de51e28 --- /dev/null +++ b/src/rpg/world/overworld.c @@ -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); +} \ No newline at end of file diff --git a/src/rpg/world/overworld.h b/src/rpg/world/overworld.h new file mode 100644 index 0000000..10d7f5b --- /dev/null +++ b/src/rpg/world/overworld.h @@ -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); \ No newline at end of file diff --git a/src/rpg/world/tile.h b/src/rpg/world/tile.h new file mode 100644 index 0000000..1e285ea --- /dev/null +++ b/src/rpg/world/tile.h @@ -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; \ No newline at end of file diff --git a/src/scene/CMakeLists.txt b/src/scene/CMakeLists.txt index 3fa686a..2f80ccf 100644 --- a/src/scene/CMakeLists.txt +++ b/src/scene/CMakeLists.txt @@ -6,7 +6,6 @@ # Sources target_sources(${DUSK_TARGET_NAME} PRIVATE - node.c ) # Subdirs diff --git a/src/scene/node.c b/src/scene/node.c deleted file mode 100644 index 6557200..0000000 --- a/src/scene/node.c +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/scene/node.h b/src/scene/node.h deleted file mode 100644 index b7929e4..0000000 --- a/src/scene/node.h +++ /dev/null @@ -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); \ No newline at end of file diff --git a/src/scene/test/scenetest.c b/src/scene/test/scenetest.c index 1ff59f9..67479dd 100644 --- a/src/scene/test/scenetest.c +++ b/src/scene/test/scenetest.c @@ -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; } \ No newline at end of file diff --git a/src/scene/test/scenetest.h b/src/scene/test/scenetest.h index 52b264d..917d5c9 100644 --- a/src/scene/test/scenetest.h +++ b/src/scene/test/scenetest.h @@ -6,7 +6,6 @@ */ #pragma once -#include "ecs/ecssystem.h" #include "display/texture/texture.h" extern texture_t test;