Area cutscene controls

This commit is contained in:
2026-07-07 07:28:12 -05:00
parent 189babd2cf
commit 6a6d8448f7
16 changed files with 363 additions and 3 deletions
+46
View File
@@ -150,3 +150,49 @@ typedef struct cutscene_s {
) \ ) \
} \ } \
} }
#define CUTSCENE_MAP_AREA_ADD( \
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, CALLBACK, NOTIFY, TRIGGER \
) \
{ \
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, \
.mapAreaAdd = { \
.min = { MIN_X, MIN_Y, MIN_Z }, \
.max = { MAX_X, MAX_Y, MAX_Z }, \
.callback = CALLBACK, \
.notify = NOTIFY, \
.trigger = TRIGGER \
} \
}
#define CUTSCENE_MAP_AREA_REMOVE(AREA_ID) \
{ \
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, \
.mapAreaRemove = { .areaId = AREA_ID } \
}
// Waits until any one of the given map area IDs has its callback invoked.
// Accepts CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
#define CUTSCENE_MAP_AREA_WAIT(...) \
{ \
.type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, \
.mapAreaWait = { \
.areaIds = (const uint8_t[]){ __VA_ARGS__ }, \
.count = (uint8_t)( \
sizeof((const uint8_t[]){ __VA_ARGS__ }) / sizeof(uint8_t) \
) \
} \
}
// Adds a map area, waits for it to be triggered once, then removes it
// before the cutscene continues. Uses a no-op callback since the wait is
// driven by the area's trigger count rather than callback logic.
#define CUTSCENE_MAP_AREA_TRIGGER_ONCE( \
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, NOTIFY, TRIGGER \
) \
CUTSCENE_MAP_AREA_ADD( \
MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, \
mapAreaNoopCallback, NOTIFY, TRIGGER \
), \
CUTSCENE_MAP_AREA_WAIT(CUTSCENE_AREA_LAST_CREATED), \
CUTSCENE_MAP_AREA_REMOVE(CUTSCENE_AREA_LAST_CREATED)
+14
View File
@@ -36,6 +36,7 @@ void cutsceneSystemStartCutsceneWith(
CUTSCENE_SYSTEM.entityInteracted = interacted; CUTSCENE_SYSTEM.entityInteracted = interacted;
CUTSCENE_SYSTEM.entityLastCreated = NULL; CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL; CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0. CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so Next wraps to 0.
cutsceneSystemNext(); cutsceneSystemNext();
} }
@@ -63,6 +64,7 @@ void cutsceneSystemNext() {
CUTSCENE_SYSTEM.entityInteracted = NULL; CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL; CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL; CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
return; return;
} }
@@ -117,6 +119,17 @@ entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) {
return entity; return entity;
} }
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId) {
if(areaId == CUTSCENE_AREA_LAST_CREATED) {
assertTrue(
CUTSCENE_SYSTEM.areaLastCreated != CUTSCENE_AREA_LAST_CREATED,
"CUTSCENE_AREA_LAST_CREATED used but no map area has been created"
);
return CUTSCENE_SYSTEM.areaLastCreated;
}
return areaId;
}
void cutsceneSystemDispose() { void cutsceneSystemDispose() {
CUTSCENE_SYSTEM.scene = NULL; CUTSCENE_SYSTEM.scene = NULL;
CUTSCENE_SYSTEM.currentItem = 0xFF; CUTSCENE_SYSTEM.currentItem = 0xFF;
@@ -125,4 +138,5 @@ void cutsceneSystemDispose() {
CUTSCENE_SYSTEM.entityInteracted = NULL; CUTSCENE_SYSTEM.entityInteracted = NULL;
CUTSCENE_SYSTEM.entityLastCreated = NULL; CUTSCENE_SYSTEM.entityLastCreated = NULL;
CUTSCENE_SYSTEM.entityLastRef = NULL; CUTSCENE_SYSTEM.entityLastRef = NULL;
CUTSCENE_SYSTEM.areaLastCreated = CUTSCENE_AREA_LAST_CREATED;
} }
+11
View File
@@ -14,6 +14,7 @@ typedef struct entity_s entity_t;
#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD) #define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD)
#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC) #define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC)
#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB) #define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB)
#define CUTSCENE_AREA_LAST_CREATED ((uint8_t)0xFF)
// Maximum number of bytes a running cutscene may request via // Maximum number of bytes a running cutscene may request via
// cutscene_t.dataSize. // cutscene_t.dataSize.
@@ -27,6 +28,7 @@ typedef struct {
entity_t *entityInteracted; entity_t *entityInteracted;
entity_t *entityLastCreated; entity_t *entityLastCreated;
entity_t *entityLastRef; entity_t *entityLastRef;
uint8_t areaLastCreated;
// Data (used by the current item). // Data (used by the current item).
cutsceneitemdata_t data; cutsceneitemdata_t data;
@@ -75,6 +77,15 @@ void cutsceneSystemStartCutsceneWith(
*/ */
entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex); entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex);
/**
* Resolves a raw map area ID (or CUTSCENE_AREA_LAST_CREATED sentinel) to
* a concrete map area ID.
*
* @param areaId Raw map area ID or sentinel value.
* @returns The resolved map area ID.
*/
uint8_t cutsceneSystemGetAreaId(const uint8_t areaId);
/** /**
* Advance to the next item in the cutscene. * Advance to the next item in the cutscene.
*/ */
@@ -12,4 +12,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
add_subdirectory(control) add_subdirectory(control)
add_subdirectory(entity) add_subdirectory(entity)
add_subdirectory(item) add_subdirectory(item)
add_subdirectory(maparea)
add_subdirectory(ui) add_subdirectory(ui)
+21
View File
@@ -65,6 +65,18 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
cutsceneEntityWalkToEntityStart(item, data); cutsceneEntityWalkToEntityStart(item, data);
break; break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
cutsceneMapAreaAddStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
cutsceneMapAreaRemoveStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
cutsceneMapAreaWaitStart(item, data);
break;
default: default:
break; break;
} }
@@ -111,6 +123,15 @@ bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data)
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY: case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
return cutsceneEntityWalkToEntityUpdate(item, data); return cutsceneEntityWalkToEntityUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
return cutsceneMapAreaAddUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
return cutsceneMapAreaRemoveUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
return cutsceneMapAreaWaitUpdate(item, data);
default: default:
return false; return false;
} }
+11 -1
View File
@@ -19,6 +19,9 @@
#include "ui/cutscenetext.h" #include "ui/cutscenetext.h"
#include "ui/cutscenefade.h" #include "ui/cutscenefade.h"
#include "item/cutsceneitemgive.h" #include "item/cutsceneitemgive.h"
#include "maparea/cutscenemapareaadd.h"
#include "maparea/cutscenemaparearemove.h"
#include "maparea/cutscenemapareawait.h"
typedef struct cutscene_s cutscene_t; typedef struct cutscene_s cutscene_t;
@@ -37,7 +40,10 @@ typedef enum {
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
CUTSCENE_ITEM_TYPE_ENTITY_ADD, CUTSCENE_ITEM_TYPE_ENTITY_ADD,
CUTSCENE_ITEM_TYPE_ENTITY_TURN, CUTSCENE_ITEM_TYPE_ENTITY_TURN,
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT
} cutsceneitemtype_t; } cutsceneitemtype_t;
struct cutsceneitem_s { struct cutsceneitem_s {
@@ -58,6 +64,9 @@ struct cutsceneitem_s {
cutsceneentityadd_t entityAdd; cutsceneentityadd_t entityAdd;
cutsceneentityturn_t entityTurn; cutsceneentityturn_t entityTurn;
cutsceneentitywalktoentity_t entityWalkToEntity; cutsceneentitywalktoentity_t entityWalkToEntity;
cutscenemapareaadd_t mapAreaAdd;
cutscenemaparearemove_t mapAreaRemove;
cutscenemapareawait_t mapAreaWait;
}; };
}; };
@@ -65,6 +74,7 @@ typedef union cutsceneitemdata_u {
cutscenewaitdata_t wait; cutscenewaitdata_t wait;
cutsceneentitywalktodata_t entityWalkTo; cutsceneentitywalktodata_t entityWalkTo;
cutsceneconcurrentdata_t concurrent; cutsceneconcurrentdata_t concurrent;
cutscenemapareawaitdata_t mapAreaWait;
} cutsceneitemdata_t; } cutsceneitemdata_t;
/** /**
@@ -0,0 +1,11 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
cutscenemapareaadd.c
cutscenemaparearemove.c
cutscenemapareawait.c
)
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h"
void cutsceneMapAreaAddStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
CUTSCENE_SYSTEM.areaLastCreated = mapAreaAdd(
item->mapAreaAdd.min,
item->mapAreaAdd.max,
item->mapAreaAdd.callback,
item->mapAreaAdd.notify,
item->mapAreaAdd.trigger
);
}
bool_t cutsceneMapAreaAddUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
return true;
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/overworld/maparea.h"
typedef struct cutsceneitem_s cutsceneitem_t;
typedef union cutsceneitemdata_u cutsceneitemdata_t;
typedef struct {
worldpos_t min;
worldpos_t max;
mapareacallback_t callback;
uint8_t notify;
uint8_t trigger;
} cutscenemapareaadd_t;
/**
* Starts a map area add step (adds the area immediately, storing its ID
* in CUTSCENE_SYSTEM.areaLastCreated).
*
* @param item The cutscene item.
* @param data Runtime data storage.
*/
void cutsceneMapAreaAddStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
/**
* Updates a map area add step (always completes immediately).
*
* @param item The cutscene item.
* @param data Runtime data storage.
* @returns true always.
*/
bool_t cutsceneMapAreaAddUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/overworld/maparea.h"
void cutsceneMapAreaRemoveStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
mapAreaRemove(cutsceneSystemGetAreaId(item->mapAreaRemove.areaId));
}
bool_t cutsceneMapAreaRemoveUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
return true;
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct cutsceneitem_s cutsceneitem_t;
typedef union cutsceneitemdata_u cutsceneitemdata_t;
typedef struct {
uint8_t areaId;
} cutscenemaparearemove_t;
/**
* Starts a map area remove step (removes the area immediately). Accepts
* CUTSCENE_AREA_LAST_CREATED in place of a literal area ID.
*
* @param item The cutscene item.
* @param data Runtime data storage.
*/
void cutsceneMapAreaRemoveStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
/**
* Updates a map area remove step (always completes immediately).
*
* @param item The cutscene item.
* @param data Runtime data storage.
* @returns true always.
*/
bool_t cutsceneMapAreaRemoveUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/cutscene/cutscenesystem.h"
#include "rpg/overworld/maparea.h"
#include "assert/assert.h"
void cutsceneMapAreaWaitStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
assertTrue(
item->mapAreaWait.count <= CUTSCENE_MAP_AREA_WAIT_MAX,
"Too many areas in CUTSCENE_MAP_AREA_WAIT"
);
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
data->mapAreaWait.baseline[i] = MAP_AREAS[areaId].triggerCount;
}
}
bool_t cutsceneMapAreaWaitUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
for(uint8_t i = 0; i < item->mapAreaWait.count; i++) {
uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]);
if(MAP_AREAS[areaId].triggerCount != data->mapAreaWait.baseline[i]) {
return true;
}
}
return false;
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct cutsceneitem_s cutsceneitem_t;
typedef union cutsceneitemdata_u cutsceneitemdata_t;
/** Maximum number of areas a single CUTSCENE_MAP_AREA_WAIT may watch. */
#define CUTSCENE_MAP_AREA_WAIT_MAX 4
typedef struct {
const uint8_t *areaIds;
uint8_t count;
} cutscenemapareawait_t;
typedef struct {
uint32_t baseline[CUTSCENE_MAP_AREA_WAIT_MAX];
} cutscenemapareawaitdata_t;
/**
* Starts a map area wait step, snapshotting each watched area's current
* trigger count.
*
* @param item The cutscene item.
* @param data Runtime data storage.
*/
void cutsceneMapAreaWaitStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
/**
* Updates a map area wait step, completing once any watched area's
* trigger count has changed since Start (i.e. its callback fired).
*
* @param item The cutscene item.
* @param data Runtime data storage.
* @returns true once any watched area has been triggered.
*/
bool_t cutsceneMapAreaWaitUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
@@ -7,9 +7,9 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#include "rpg/cutscene/cutscene.h"
typedef struct entity_s entity_t; typedef struct entity_s entity_t;
typedef struct cutscene_s cutscene_t;
/** /**
* Describes the type of interaction an entity supports. * Describes the type of interaction an entity supports.
+5
View File
@@ -35,6 +35,7 @@ void mapAreaInit(
area->callback = callback; area->callback = callback;
area->notify = notify; area->notify = notify;
area->trigger = trigger; area->trigger = trigger;
area->triggerCount = 0;
memorySet(area->entities, 0, sizeof(area->entities)); memorySet(area->entities, 0, sizeof(area->entities));
} }
@@ -140,6 +141,10 @@ void mapAreaCheckEntity(entity_t *entity) {
} }
if(trigger == 0 || !(area->trigger & trigger)) continue; if(trigger == 0 || !(area->trigger & trigger)) continue;
area->triggerCount++;
area->callback(entity, trigger); area->callback(entity, trigger);
} }
} }
void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger) {
}
+15
View File
@@ -38,6 +38,11 @@ typedef struct maparea_s {
uint8_t notify; uint8_t notify;
uint8_t trigger; uint8_t trigger;
uint8_t entities[ENTITY_COUNT]; uint8_t entities[ENTITY_COUNT];
// Incremented every time this area's callback is invoked. Lets other
// systems (e.g. cutscenes) detect "has this area fired since I last
// checked" without needing to be the callback themselves.
uint32_t triggerCount;
} maparea_t; } maparea_t;
extern maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX]; extern maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX];
@@ -144,3 +149,13 @@ void mapAreaRemove(const uint8_t id);
* @param entity Pointer to the entity to check. * @param entity Pointer to the entity to check.
*/ */
void mapAreaCheckEntity(entity_t *entity); void mapAreaCheckEntity(entity_t *entity);
/**
* A map area callback that does nothing. Useful for areas whose only
* purpose is to be waited on (see CUTSCENE_MAP_AREA_WAIT), since the
* wait is driven by triggerCount rather than callback logic.
*
* @param entity Pointer to the entity associated with the callback.
* @param trigger Which MAP_TRIGGER_* condition invoked the callback.
*/
void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger);