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