Events base.

This commit is contained in:
2025-06-25 14:03:32 -05:00
parent 6ba03235dd
commit 577bef8fb7
17 changed files with 269 additions and 76 deletions

View File

@ -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"
}
]
}

View File

@ -37,7 +37,7 @@
"overworld.tsx"
],
"project": "map project.tiled-project",
"property.type": "tileSolidType",
"property.type": "string",
"recentFiles": [
"overworld.tsx",
"map.tmj",

View File

@ -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,

View File

@ -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)

View File

@ -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);
}

View File

@ -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;

View File

@ -0,0 +1,11 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
event.c
eventtext.c
)

View File

@ -5,4 +5,69 @@
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#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);
}

View File

@ -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;
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();

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "eventitem.h"
typedef struct {
uint8_t itemCount;
eventitem_t items[EVENT_ITEM_COUNT_MAX];
} eventdata_t;

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma 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

View File

@ -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();
}
}

View File

@ -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];
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);

View File

@ -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();

View File

@ -188,5 +188,4 @@ void uiTextboxSetText(const char_t *text) {
assertUnreachable("Code should not reach here, all cases handled.");
}
printf("test");
}

View File

@ -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)
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")

View File

@ -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