From 577bef8fb7ba5e6115af7e91c7b27075e0c6840d Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 25 Jun 2025 14:03:32 -0500 Subject: [PATCH] Events base. --- data/events/test_event.json | 11 +++-- data/map project.tiled-session | 2 +- data/map.tmj | 7 +++- src/dusk/CMakeLists.txt | 1 + src/dusk/entity/npc.c | 33 +-------------- src/dusk/entity/npc.h | 5 ++- src/dusk/event/CMakeLists.txt | 11 +++++ src/dusk/event/event.c | 67 +++++++++++++++++++++++++++++- src/dusk/event/event.h | 55 +++++++++++++++--------- src/dusk/event/eventdata.h | 14 +++++++ src/dusk/event/eventitem.h | 26 ++++++++++++ src/dusk/event/eventtext.c | 26 ++++++++++++ src/dusk/event/eventtext.h | 23 +++++++++- src/dusk/main.c | 4 +- src/dusk/ui/uitextbox.c | 1 - tools/eventcompile/eventcompile.py | 31 ++++++++++---- tools/mapcompile/entityParser.py | 28 ++++++++++++- 17 files changed, 269 insertions(+), 76 deletions(-) create mode 100644 src/dusk/event/CMakeLists.txt create mode 100644 src/dusk/event/eventdata.h create mode 100644 src/dusk/event/eventitem.h create mode 100644 src/dusk/event/eventtext.c diff --git a/data/events/test_event.json b/data/events/test_event.json index c23ed8d..0d2e838 100644 --- a/data/events/test_event.json +++ b/data/events/test_event.json @@ -3,12 +3,11 @@ "items": [ { "type": "text", - "texts": [ - "This is a bucket", - "Dear God", - "There's more!", - "No!" - ] + "text": "This is a bucket" + }, + { + "type": "text", + "text": "Dear God" } ] } \ No newline at end of file diff --git a/data/map project.tiled-session b/data/map project.tiled-session index 5157b4f..921a6ac 100644 --- a/data/map project.tiled-session +++ b/data/map project.tiled-session @@ -37,7 +37,7 @@ "overworld.tsx" ], "project": "map project.tiled-project", - "property.type": "tileSolidType", + "property.type": "string", "recentFiles": [ "overworld.tsx", "map.tmj", diff --git a/data/map.tmj b/data/map.tmj index 739284f..c310d4a 100644 --- a/data/map.tmj +++ b/data/map.tmj @@ -425,6 +425,11 @@ { "id":13, "properties":[ + { + "name":"interactEvent", + "type":"string", + "value":"test_event" + }, { "name":"interactText", "type":"string", @@ -434,7 +439,7 @@ "name":"interactType", "propertytype":"npcInteractType", "type":"string", - "value":"NPC_INTERACT_TYPE_TEXT" + "value":"NPC_INTERACT_TYPE_EVENT" }], "template":"templates\/NPC.tx", "x":6539.95833333333, diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index dbb7017..9fc6bf7 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(${DUSK_TARGET_NAME} add_subdirectory(assert) add_subdirectory(display) add_subdirectory(entity) +add_subdirectory(event) add_subdirectory(item) add_subdirectory(locale) add_subdirectory(physics) diff --git a/src/dusk/entity/npc.c b/src/dusk/entity/npc.c index d576077..3065f8b 100644 --- a/src/dusk/entity/npc.c +++ b/src/dusk/entity/npc.c @@ -37,41 +37,10 @@ void npcInteract(entity_t *player, entity_t *self) { break; case NPC_INTERACT_TYPE_EVENT: + eventSetActive(self->npc.eventData); break; default: assertUnreachable("Unknown NPC interaction type"); } - - // uiTextboxSetText( - // "Hello World how are you today? Hope things are going well for you! I am " - // "having a great day, hope you are too. Did I ever tell you about the tragedy " - // "of Darth Plagueis the Wise? He was a dark lord of the Sith, " - // "so powerful and so wise he could use the Force to influence the midichlorians" - // ); - // const char_t *name = "Dom"; - // const char_t *key = "name"; - // uint16_t len = languageFormat( - // "test.npc.text", - // NULL, - // 0, - // (const char_t *[]){ key }, - // (const char_t *[]){ name }, - // 1 - // ); - - // char_t *buffer = malloc(sizeof(char_t) + len + 1); - // assertNotNull(buffer, "Failed to allocate buffer for NPC text"); - - // languageFormat( - // "test.npc.text", - // buffer, - // len + 1, - // (const char_t *[]){ key }, - // (const char_t *[]){ name }, - // 1 - // ); - - // uiTextboxSetText(buffer); - // free(buffer); } \ No newline at end of file diff --git a/src/dusk/entity/npc.h b/src/dusk/entity/npc.h index ad03843..bc903d9 100644 --- a/src/dusk/entity/npc.h +++ b/src/dusk/entity/npc.h @@ -1,6 +1,6 @@ #pragma once -#include "dusk.h" +#include "event/eventlist.h" typedef struct _entity_t entity_t; @@ -15,7 +15,8 @@ typedef struct { npcinteracttype_t interactType; union { - char_t* text; + const char_t* text; + const eventdata_t *eventData; }; } npc_t; diff --git a/src/dusk/event/CMakeLists.txt b/src/dusk/event/CMakeLists.txt new file mode 100644 index 0000000..b052311 --- /dev/null +++ b/src/dusk/event/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 + event.c + eventtext.c +) \ No newline at end of file diff --git a/src/dusk/event/event.c b/src/dusk/event/event.c index 0def703..fd391c8 100644 --- a/src/dusk/event/event.c +++ b/src/dusk/event/event.c @@ -5,4 +5,69 @@ * https://opensource.org/licenses/MIT */ -#include "event.h" \ No newline at end of file +#include "event.h" +#include "util/memory.h" +#include "assert/assert.h" + +eventcallback_t EVENT_CALLBACKS[] = { + [EVENT_TYPE_NULL] = { NULL, NULL }, + [EVENT_TYPE_TEXT] = { eventTextStart, eventTextUpdate }, +}; + +event_t EVENT; + +void eventInit() { + memoryZero(&EVENT, sizeof(event_t)); +} + +void eventUpdate() { + if(EVENT.active == NULL) { + return; // No active event to update + } + + const eventitem_t *item = &EVENT.active->items[EVENT.item]; + assertNotNull( + EVENT_CALLBACKS[item->type].update, + "Event type does not have an update callback" + ); + + EVENT_CALLBACKS[item->type].update(item); +} + +void eventSetActive(const eventdata_t *event) { + assertNotNull(event, "Event data cannot be NULL"); + assertTrue( + event->itemCount <= EVENT_ITEM_COUNT_MAX, + "Event count too high" + ); + assertTrue(event->itemCount > 0, "Event must have at least one item"); + + EVENT.active = event; + EVENT.item = 0; + + const eventitem_t *firstItem = &EVENT.active->items[EVENT.item]; + + assertNotNull( + EVENT_CALLBACKS[firstItem->type].start, + "Event type does not have a start callback" + ); + EVENT_CALLBACKS[firstItem->type].start(firstItem); +} + +void eventNext() { + assertNotNull(EVENT.active, "No active event to proceed with"); + assertTrue(EVENT.item < EVENT.active->itemCount, "No more items in the event"); + + EVENT.item++; + if (EVENT.item >= EVENT.active->itemCount) { + EVENT.active = NULL; + return; + } + + const eventitem_t *nextItem = &EVENT.active->items[EVENT.item]; + assertNotNull( + EVENT_CALLBACKS[nextItem->type].start, + "Event type does not have a start callback" + ); + EVENT_CALLBACKS[nextItem->type].start(nextItem); +} \ No newline at end of file diff --git a/src/dusk/event/event.h b/src/dusk/event/event.h index edab15b..42ede28 100644 --- a/src/dusk/event/event.h +++ b/src/dusk/event/event.h @@ -6,24 +6,41 @@ */ #pragma once -#include "eventtext.h" - -typedef enum { - EVENT_TYPE_NULL = 0, - EVENT_TYPE_TEXT, -} eventtype_t; - -typedef struct _eventitem_t { - eventtype_t type; - - union { - eventtext_t texts; - }; -} eventitem_t; - -#define EVENT_ITEM_COUNT_MAX 32 +#include "eventdata.h" typedef struct { - uint8_t itemCount; - eventitem_t items[EVENT_ITEM_COUNT_MAX]; -} event_t; \ No newline at end of file + eventdata_t data; + const eventdata_t *active; + uint8_t item; +} event_t; + +typedef struct { + void (*start)(const eventitem_t *item); + void (*update)(const eventitem_t *item); +} eventcallback_t; + +extern eventcallback_t EVENT_CALLBACKS[EVENT_TYPE_COUNT]; +extern event_t EVENT; + +/** + * Initializes the event system. + */ +void eventInit(); + +/** + * Updates the active event. + */ +void eventUpdate(); + +/** + * Sets the active event. + * + * @param event The event to set as active. + */ +void eventSetActive(const eventdata_t *eventData); + +/** + * Goes to the next item in the active event. Only meant to be called by + * event items. + */ +void eventNext(); \ No newline at end of file diff --git a/src/dusk/event/eventdata.h b/src/dusk/event/eventdata.h new file mode 100644 index 0000000..75799a0 --- /dev/null +++ b/src/dusk/event/eventdata.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 "eventitem.h" + +typedef struct { + uint8_t itemCount; + eventitem_t items[EVENT_ITEM_COUNT_MAX]; +} eventdata_t; diff --git a/src/dusk/event/eventitem.h b/src/dusk/event/eventitem.h new file mode 100644 index 0000000..3174f31 --- /dev/null +++ b/src/dusk/event/eventitem.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma ocne +#include "eventtext.h" + +typedef enum { + EVENT_TYPE_NULL = 0, + EVENT_TYPE_TEXT, +} eventtype_t; + +#define EVENT_TYPE_COUNT 2 + +typedef struct _eventitem_t { + eventtype_t type; + + union { + eventtext_t text; + }; +} eventitem_t; + +#define EVENT_ITEM_COUNT_MAX 32 \ No newline at end of file diff --git a/src/dusk/event/eventtext.c b/src/dusk/event/eventtext.c new file mode 100644 index 0000000..f9eee4c --- /dev/null +++ b/src/dusk/event/eventtext.c @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "event.h" +#include "ui/uitextbox.h" +#include "assert/assert.h" + +void eventTextStart(const eventitem_t *item) { + assertNotNull(item, "Event item cannot be NULL"); + assertTrue(item->type == EVENT_TYPE_TEXT, "Event item must be of type TEXT"); + assertNotNull(item->text, "Event item must have at least one text"); + uiTextboxSetText(item->text); +} + +void eventTextUpdate(const eventitem_t *item) { + assertNotNull(item, "Event item cannot be NULL"); + assertTrue(item->type == EVENT_TYPE_TEXT, "Event item must be of type TEXT"); + + if(!UI_TEXTBOX.visible) { + eventNext(); + } +} \ No newline at end of file diff --git a/src/dusk/event/eventtext.h b/src/dusk/event/eventtext.h index 2ebd7ac..bddb2ca 100644 --- a/src/dusk/event/eventtext.h +++ b/src/dusk/event/eventtext.h @@ -12,4 +12,25 @@ typedef struct _eventitem_t eventitem_t; #define EVENT_TEXT_STRING_COUNT_MAX 8 -typedef const char_t* eventtext_t[EVENT_TEXT_STRING_COUNT_MAX]; \ No newline at end of file +typedef const char_t* eventtext_t; + +/** + * Starts the text event for the given item. + * + * @param item The event item to start. + */ +void eventTextStart(const eventitem_t *item); + +/** + * Updates the text event for the given item. + * + * @param item The event item to update. + */ +void eventTextUpdate(const eventitem_t *item); + +/** + * Query whether the text event is done or not. + * + * @param item The event item to check. + */ +bool_t eventTextIsDone(const eventitem_t *item); \ No newline at end of file diff --git a/src/dusk/main.c b/src/dusk/main.c index 2c001cd..8cf791d 100644 --- a/src/dusk/main.c +++ b/src/dusk/main.c @@ -11,13 +11,14 @@ #include "display/scene.h" #include "world/overworld.h" #include "input.h" - +#include "event/event.h" #include "ui/uitextbox.h" // Press F5 to compile and run the compiled game.gb in the Emulicous Emulator/Debugger void main(void) { renderInit(); inputInit(); + eventInit(); uiTextboxInit(); overworldInit(); @@ -30,6 +31,7 @@ void main(void) { overworldUpdate(); uiTextboxUpdate(); + eventUpdate(); // Update input for next frame. inputUpdate(); diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index 9900d64..aebb79a 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -188,5 +188,4 @@ void uiTextboxSetText(const char_t *text) { assertUnreachable("Code should not reach here, all cases handled."); } - printf("test"); } \ No newline at end of file diff --git a/tools/eventcompile/eventcompile.py b/tools/eventcompile/eventcompile.py index 1dc9ec7..361bb54 100644 --- a/tools/eventcompile/eventcompile.py +++ b/tools/eventcompile/eventcompile.py @@ -55,8 +55,8 @@ for jsonFile in jsonFiles: f.write(f"// Generated event header for {jsonFile}\n") f.write(f"// Generated at {now}\n") f.write("#pragma once\n\n") - f.write("#include \"event/event.h\"\n\n") - f.write(f"static const event_t EVENT_{key.upper()} = {{\n") + f.write("#include \"event/eventdata.h\"\n\n") + f.write(f"static const eventdata_t EVENT_{key.upper()} = {{\n") f.write(f" .itemCount = {len(data['items'])},\n") f.write(f" .items = {{\n") for i, item in enumerate(data['items']): @@ -69,14 +69,11 @@ for jsonFile in jsonFiles: # Text(s) Type if itemType == 'text': - if 'texts' not in item or not isinstance(item['texts'], list) or len(item['texts']) == 0: - print(f"Error: Item {i} in '{jsonFile}' of type 'text' does not contain 'texts' field.") + if 'text' not in item: + print(f"Error: Item {i} in '{jsonFile}' of type 'text' does not contain 'text' field.") sys.exit(1) f.write(f" .type = EVENT_TYPE_TEXT,\n") - f.write(f" .texts = {{\n") - for text in item['texts']: - f.write(f" \"{text}\",\n") - f.write(f" }},\n") + f.write(f" .text = \"{item['text']}\",\n") else: @@ -87,4 +84,20 @@ for jsonFile in jsonFiles: f.write(f" }},\n") f.write(f"}};\n\n") - eventFiles.append(key) \ No newline at end of file + eventFiles.append(key) + +# Write the event list header +eventListFile = os.path.join(outputDir, "eventlist.h") +with open(eventListFile, 'w', encoding='utf-8') as f: + f.write(f"// Generated event list header\n") + f.write(f"// Generated at {now}\n") + f.write("#pragma once\n\n") + f.write("#include \"event/event.h\"\n") + for event in eventFiles: + f.write(f"#include \"event/{event}.h\"\n") + f.write("\n") + f.write(f"#define EVENT_LIST_COUNT {len(eventFiles)}\n\n") + f.write("static const eventdata_t* EVENT_LIST[EVENT_LIST_COUNT] = {\n") + for event in eventFiles: + f.write(f" &EVENT_{event.upper()},\n") + f.write("};\n\n") \ No newline at end of file diff --git a/tools/mapcompile/entityParser.py b/tools/mapcompile/entityParser.py index b4130b5..53336e5 100644 --- a/tools/mapcompile/entityParser.py +++ b/tools/mapcompile/entityParser.py @@ -30,11 +30,35 @@ def parseEntity(obj, chunkData): obj['dir'] = 'ENTITY_DIR_SOUTH' obj['type'] = entType + def getProperty(propName): + for prop in obj['properties']: + if prop['name'] == propName: + return prop['value'] + return None + # Handle per-type properties if entType == 'ENTITY_TYPE_NPC': + interactType = getProperty('interactType') + if interactType is None: + print(f"NPC entity missing 'interactType' property: {obj['id']}") + sys.exit(1) + obj['data'] = {} obj['data']['npc'] = {} - obj['data']['npc']['interactType'] = 'NPC_INTERACT_TYPE_TEXT' - obj['data']['npc']['text'] = '"test.npc.text"' + obj['data']['npc']['interactType'] = interactType + + if interactType == 'NPC_INTERACT_TYPE_TEXT': + text = getProperty('interactText') + if text is None: + print(f"NPC entity missing 'interactText' property: {obj['id']}") + sys.exit(1) + obj['data']['npc']['text'] = text + + elif interactType == 'NPC_INTERACT_TYPE_EVENT': + event = getProperty('interactEvent') + if event is None: + print(f"NPC entity missing 'interactEvent' property: {obj['id']}") + sys.exit(1) + obj['data']['npc']['eventData'] = f'&EVENT_{event.upper()}' return obj \ No newline at end of file