diff --git a/src/rpg/CMakeLists.txt b/src/rpg/CMakeLists.txt index 652b03e..d1b7fd3 100644 --- a/src/rpg/CMakeLists.txt +++ b/src/rpg/CMakeLists.txt @@ -11,5 +11,6 @@ target_sources(${DUSK_TARGET_NAME} ) # Subdirs +add_subdirectory(cutscene) add_subdirectory(entity) add_subdirectory(world) \ No newline at end of file diff --git a/src/rpg/cutscene/CMakeLists.txt b/src/rpg/cutscene/CMakeLists.txt new file mode 100644 index 0000000..a2d1b86 --- /dev/null +++ b/src/rpg/cutscene/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 + cutscenesystem.c + cutscenemode.c +) + +# Subdirs +add_subdirectory(item) \ No newline at end of file diff --git a/src/rpg/cutscene/cutscene.h b/src/rpg/cutscene/cutscene.h new file mode 100644 index 0000000..7df18ad --- /dev/null +++ b/src/rpg/cutscene/cutscene.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 "rpg/cutscene/item/cutsceneitem.h" + +typedef struct cutscene_s { + const cutsceneitem_t *items; + uint8_t itemCount; +} cutscene_t; \ No newline at end of file diff --git a/src/rpg/cutscene/cutscenemode.c b/src/rpg/cutscene/cutscenemode.c new file mode 100644 index 0000000..39c10b3 --- /dev/null +++ b/src/rpg/cutscene/cutscenemode.c @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/cutscenesystem.h" + +bool_t cutsceneModeIsInputAllowed() { + switch(CUTSCENE_SYSTEM.mode) { + case CUTSCENE_MODE_FULL_FREEZE: + case CUTSCENE_MODE_INPUT_FREEZE: + return false; + + default: + return true; + } +} \ No newline at end of file diff --git a/src/rpg/cutscene/cutscenemode.h b/src/rpg/cutscene/cutscenemode.h new file mode 100644 index 0000000..cf20a3d --- /dev/null +++ b/src/rpg/cutscene/cutscenemode.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" + +typedef enum { + CUTSCENE_MODE_NONE, + CUTSCENE_MODE_FULL_FREEZE, + CUTSCENE_MODE_INPUT_FREEZE, + CUTSCENE_MODE_GAMEPLAY +} cutscenemode_t; + +// Default mode for all cutscenes. +#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE + +/** + * Check if input is allowed in the current cutscene mode. + * + * @return true if input is allowed, false otherwise. + */ +bool_t cutsceneModeIsInputAllowed(); \ No newline at end of file diff --git a/src/rpg/cutscene/cutscenesystem.c b/src/rpg/cutscene/cutscenesystem.c new file mode 100644 index 0000000..d803e05 --- /dev/null +++ b/src/rpg/cutscene/cutscenesystem.c @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "cutscenesystem.h" +#include "util/memory.h" + +cutscenesystem_t CUTSCENE_SYSTEM; + +void cutsceneSystemInit() { + memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); +} + +void cutsceneSystemStartCutscene(const cutscene_t *cutscene) { + CUTSCENE_SYSTEM.scene = cutscene; + CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL; + CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps. + cutsceneSystemNext(); +} + +void cutsceneSystemUpdate() { + if(CUTSCENE_SYSTEM.scene == NULL) return; + + const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); + cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data); +} + +void cutsceneSystemNext() { + if(CUTSCENE_SYSTEM.scene == NULL) return; + + CUTSCENE_SYSTEM.currentItem++; + + // End of the cutscene? + if( + CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount + ) { + CUTSCENE_SYSTEM.scene = NULL; + CUTSCENE_SYSTEM.currentItem = 0xFF; + CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE; + return; + } + + // Start item. + const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); + memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data)); + cutsceneItemStart(item, &CUTSCENE_SYSTEM.data); +} + +const cutsceneitem_t * cutsceneSystemGetCurrentItem() { + if(CUTSCENE_SYSTEM.scene == NULL) return NULL; + + return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem]; +} \ No newline at end of file diff --git a/src/rpg/cutscene/cutscenesystem.h b/src/rpg/cutscene/cutscenesystem.h new file mode 100644 index 0000000..7c3242a --- /dev/null +++ b/src/rpg/cutscene/cutscenesystem.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "cutscene.h" +#include "cutscenemode.h" + +typedef struct { + const cutscene_t *scene; + uint8_t currentItem; + + // Data (used by the current item). + cutsceneitemdata_t data; + cutscenemode_t mode; +} cutscenesystem_t; + +extern cutscenesystem_t CUTSCENE_SYSTEM; + +/** + * Initialize the cutscene system. + */ +void cutsceneSystemInit(); + +/** + * Start a cutscene. + * + * @param cutscene Pointer to the cutscene to start. + */ +void cutsceneSystemStartCutscene(const cutscene_t *cutscene); + +/** + * Advance to the next item in the cutscene. + */ +void cutsceneSystemNext(); + +/** + * Update the cutscene system for one frame. + */ +void cutsceneSystemUpdate(); + +/** + * Get the current cutscene item. + * + * @return Pointer to the current cutscene item. + */ +const cutsceneitem_t * cutsceneSystemGetCurrentItem(); \ No newline at end of file diff --git a/src/rpg/cutscene/item/CMakeLists.txt b/src/rpg/cutscene/item/CMakeLists.txt new file mode 100755 index 0000000..114d521 --- /dev/null +++ b/src/rpg/cutscene/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 + cutsceneitem.c +) \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutscenecallback.h b/src/rpg/cutscene/item/cutscenecallback.h new file mode 100644 index 0000000..494f877 --- /dev/null +++ b/src/rpg/cutscene/item/cutscenecallback.h @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef void (*cutscenecallback_t)(void); \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutscenecutscene.h b/src/rpg/cutscene/item/cutscenecutscene.h new file mode 100644 index 0000000..6588720 --- /dev/null +++ b/src/rpg/cutscene/item/cutscenecutscene.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct cutscene_s cutscene_t; + +typedef cutscene_t* cutscenecutscene_t; \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutsceneitem.c b/src/rpg/cutscene/item/cutsceneitem.c new file mode 100644 index 0000000..5e615f5 --- /dev/null +++ b/src/rpg/cutscene/item/cutsceneitem.c @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/cutscenesystem.h" +#include "input/input.h" +#include "time/time.h" + +void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { + switch(item->type) { + case CUTSCENE_ITEM_TYPE_TEXT: { + strncpy(data->text.buffer, item->text, CUTSCENE_TEXT_BUFFER); + data->text.length = strlen(data->text.buffer); + break; + } + + case CUTSCENE_ITEM_TYPE_WAIT: + data->wait = item->wait; + break; + + case CUTSCENE_ITEM_TYPE_CALLBACK: + if(item->callback != NULL) item->callback(); + break; + + case CUTSCENE_ITEM_TYPE_CUTSCENE: + if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene); + break; + + default: + break; + } +} + +void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { + switch(item->type) { + case CUTSCENE_ITEM_TYPE_WAIT: + data->wait -= TIME.fixedDelta; + if(data->wait <= 0) cutsceneSystemNext(); + break; + + default: + break; + } +} \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutsceneitem.h b/src/rpg/cutscene/item/cutsceneitem.h new file mode 100644 index 0000000..39b9818 --- /dev/null +++ b/src/rpg/cutscene/item/cutsceneitem.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "cutscenewait.h" +#include "cutscenecallback.h" +#include "cutscenetext.h" +#include "cutscenecutscene.h" + +typedef enum { + CUTSCENE_ITEM_TYPE_NULL, + CUTSCENE_ITEM_TYPE_TEXT, + CUTSCENE_ITEM_TYPE_CALLBACK, + CUTSCENE_ITEM_TYPE_WAIT, + CUTSCENE_ITEM_TYPE_CUTSCENE +} cutsceneitemtype_t; + +typedef struct cutsceneitem_s { + cutsceneitemtype_t type; + + // Arguments/Data that will be used when this item is invoked. + union { + cutscenetext_t text; + cutscenecallback_t callback; + cutscenewait_t wait; + cutscenecutscene_t cutscene; + }; +} cutsceneitem_t; + +typedef union { + cutscenetextdata_t text; + cutscenewaitdata_t wait; +} cutsceneitemdata_t; + +/** + * Start the given cutscene item. + * + * @param item The cutscene item to start. + * @param data The cutscene item data storage. + */ +void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data); + +/** + * Tick the given cutscene item (one frame). + * + * @param item The cutscene item to tick. + * @param data The cutscene item data storage. + */ +void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data); \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutscenetext.h b/src/rpg/cutscene/item/cutscenetext.h new file mode 100644 index 0000000..dfb9f3c --- /dev/null +++ b/src/rpg/cutscene/item/cutscenetext.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +#define CUTSCENE_TEXT_BUFFER 256 + +typedef const char_t *cutscenetext_t; + +typedef struct cutscenetextdata_s { + char_t buffer[CUTSCENE_TEXT_BUFFER]; + uint8_t length; + uint8_t scroll; +} cutscenetextdata_t; \ No newline at end of file diff --git a/src/rpg/cutscene/item/cutscenewait.h b/src/rpg/cutscene/item/cutscenewait.h new file mode 100644 index 0000000..9d590df --- /dev/null +++ b/src/rpg/cutscene/item/cutscenewait.h @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef float_t cutscenewait_t; +typedef float_t cutscenewaitdata_t; \ No newline at end of file diff --git a/src/rpg/cutscene/scene/testcutscene.h b/src/rpg/cutscene/scene/testcutscene.h new file mode 100755 index 0000000..9bf776f --- /dev/null +++ b/src/rpg/cutscene/scene/testcutscene.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 "rpg/cutscene/cutscenesystem.h" + +static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = { + { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = "This is a test cutscene." }, + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, + { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = "It has multiple lines of text.\nAnd waits in between." }, +}; + +static const cutscene_t TEST_CUTSCENE_ONE = { + .items = TEST_CUTSCENE_ONE_ITEMS, + .itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t) +}; + +static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = { + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, + // { .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE }, +}; + +static const cutscene_t TEST_CUTSCENE = { + .items = TEST_CUTSCENE_TWO_ITEMS, + .itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t) +}; \ No newline at end of file diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c index 091c594..09fe998 100644 --- a/src/rpg/entity/entity.c +++ b/src/rpg/entity/entity.c @@ -10,6 +10,7 @@ #include "util/memory.h" #include "time/time.h" #include "util/math.h" +#include "rpg/cutscene/cutscenemode.h" entity_t ENTITIES[ENTITY_COUNT]; @@ -43,7 +44,10 @@ void entityUpdate(entity_t *entity) { } // Movement code. - if(ENTITY_CALLBACKS[entity->type].movement != NULL) { + if( + cutsceneModeIsInputAllowed() && + ENTITY_CALLBACKS[entity->type].movement != NULL + ) { ENTITY_CALLBACKS[entity->type].movement(entity); } } diff --git a/src/rpg/entity/npc.c b/src/rpg/entity/npc.c index 662a47b..3ab78b5 100644 --- a/src/rpg/entity/npc.c +++ b/src/rpg/entity/npc.c @@ -8,6 +8,8 @@ #include "entity.h" #include "assert/assert.h" +#include "rpg/cutscene/scene/testcutscene.h" + void npcInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); } @@ -20,7 +22,7 @@ bool_t npcInteract(entity_t *player, entity_t *npc) { assertNotNull(player, "Player entity pointer cannot be NULL"); assertNotNull(npc, "NPC entity pointer cannot be NULL"); - printf("npc interact\n"); + cutsceneSystemStartCutscene(&TEST_CUTSCENE); return false; }; \ No newline at end of file diff --git a/src/rpg/rpg.c b/src/rpg/rpg.c index 700303b..1d6b5cc 100644 --- a/src/rpg/rpg.c +++ b/src/rpg/rpg.c @@ -8,6 +8,7 @@ #include "rpg.h" #include "entity/entity.h" #include "rpg/world/world.h" +#include "rpg/cutscene/cutscenesystem.h" #include "time/time.h" #include "rpgcamera.h" #include "util/memory.h" @@ -15,6 +16,9 @@ errorret_t rpgInit(void) { memoryZero(ENTITIES, sizeof(ENTITIES)); + // Init cutscene subsystem + cutsceneSystemInit(); + // Init the world. worldInit(); @@ -31,7 +35,7 @@ errorret_t rpgInit(void) { ent = &ENTITIES[1]; entityInit(ent, ENTITY_TYPE_NPC); - ent->position.x = 6, ent->position.y = 6; + ent->position.x = 6, ent->position.y = 4; // All Good! errorOk(); @@ -51,6 +55,9 @@ void rpgUpdate(void) { if(ent->type == ENTITY_TYPE_NULL) continue; entityUpdate(ent); } while(++ent < &ENTITIES[ENTITY_COUNT]); + + // Tick the cutscene system. + cutsceneSystemUpdate(); } void rpgDispose(void) {