diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index 3076015e..a307d7af 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -1,6 +1,6 @@ /** * Copyright (c) 2025 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -19,6 +19,18 @@ typedef struct cutscene_s { size_t dataSize; } cutscene_t; +/** + * Declares a standalone, named cutscene_t (and its backing item array) - + * the entry point for a C-authored cutscene. + * + * @param NAME Suffix for the generated CUTSCENE_##NAME cutscene/items + * statics. + * @param SIZE Bytes of custom user data this cutscene needs, carved out + * of CUTSCENE_SYSTEM.data while it runs. + * @param PAUSE_TYPE Suffix of the CUTSCENE_PAUSE_* flag(s) to apply + * while this cutscene runs (e.g. NONE, ALL). + * @param ... The cutscene's cutsceneitem_t entries, in order. + */ #define CUTSCENE(NAME, SIZE, PAUSE_TYPE, ...) \ static const cutsceneitem_t CUTSCENE_##NAME##_ITEMS[] = { __VA_ARGS__ }; \ static const cutscene_t CUTSCENE_##NAME = { \ @@ -28,486 +40,25 @@ typedef struct cutscene_s { .dataSize = SIZE \ }; +/** + * References a CUTSCENE(NAME, ...)-declared cutscene by name. + * + * @param CUTSCENE The cutscene's NAME, as passed to CUTSCENE(...). + */ #define CUTSCENE_REFERENCE(CUTSCENE) \ &CUTSCENE_##CUTSCENE -#define CUTSCENE_TEXT(TEXT) \ - { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = TEXT } } - -#define CUTSCENE_TEXT_MINI(TEXT, X, Y, Z, DURATION) \ - { \ - .type = CUTSCENE_ITEM_TYPE_TEXT_MINI, \ - .textMini = { \ - .text = TEXT, \ - .position = { X, Y, Z }, \ - .duration = DURATION \ - } \ - } - -#define CUTSCENE_TEXT_MINI_HIDE(INDEX) \ - { \ - .type = CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, \ - .textMiniHide = { .index = INDEX } \ - } - -#define CUTSCENE_WAIT(WAIT) \ - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = WAIT } - -// A named, otherwise no-op position in the item list that cutsceneGoTo -// can jump execution straight to. NAME is matched with stringEquals, -// not pointer identity, so it's safe to use separate string literals -// with the same contents at the marker and at each call site. -#define CUTSCENE_MARKER(NAME) \ - { .type = CUTSCENE_ITEM_TYPE_MARKER, .marker = { .name = NAME } } - -// Restarts the currently running cutscene from its first item, -// preserving whatever interact/interacted entities triggered it. -#define CUTSCENE_RESTART() \ - { .type = CUTSCENE_ITEM_TYPE_RESTART } - -// Blocks the cutscene here indefinitely - it never completes on its own, -// only cutsceneGoTo (called externally, e.g. from a UI callback) can move -// execution past it. -#define CUTSCENE_IDLE() \ - { .type = CUTSCENE_ITEM_TYPE_IDLE } - -// Opens a full-screen UI panel (e.g. the main menu) declaratively, then -// immediately continues on to whatever follows this item. -#define CUTSCENE_UI_SHOW(SCREEN) \ - { .type = CUTSCENE_ITEM_TYPE_UI_SHOW, .uiShow = { .screen = SCREEN } } - -// Requests a switch to a different SCENE_TYPE via sceneSet, then -// immediately continues on to whatever follows this item - the switch -// itself doesn't happen until the next sceneUpdate() tick, so it does -// not take effect this frame. -#define CUTSCENE_SCENE(TYPE) \ - { .type = CUTSCENE_ITEM_TYPE_SCENE, .sceneChange = { .type = TYPE } } - +/** + * Hands control over to another CUTSCENE(NAME, ...)-declared cutscene. + * Has no dedicated item header of its own - cutscenecutsceneref_t is + * defined inline in cutsceneitem.h, tightly bound to the asset/loader + * system, so this shorthand stays here too. + * + * @param CUTSCENE The referenced cutscene's NAME, as passed to + * CUTSCENE(...). + */ #define CUTSCENE_CUTSCENE(CUTSCENE) \ { \ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, \ .cutsceneRef = { .cutscene = CUTSCENE_REFERENCE(CUTSCENE), .name = NULL } \ } - -#define CUTSCENE_CALLBACK(CALLBACK) \ - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = CALLBACK } - -#define CUTSCENE_PRINT(TEXT) \ - { .type = CUTSCENE_ITEM_TYPE_PRINT, .print = { .text = TEXT } } - -// Shows a message-only modal (no option buttons) and immediately -// continues on to whatever follows this item - it does not wait for -// the dialog to be dismissed. Script the rest of the interaction (e.g. -// CUTSCENE_CALLBACK to kick off work, CUTSCENE_WAIT, then -// CUTSCENE_MODAL_CLOSE) as later items in the same cutscene. -// TITLE and MESSAGE are each displayed as-is unless they match a -// locale message ID, in which case the translated string is shown -// instead - see uiModalLocalize. -#define CUTSCENE_MODAL(TITLE, MESSAGE) \ - { \ - .type = CUTSCENE_ITEM_TYPE_MODAL, \ - .modal = { .title = TITLE, .message = MESSAGE } \ - } - -// Shows a modal with option buttons and immediately continues on, same -// as CUTSCENE_MODAL - it does not block waiting for a selection. -// Back/cancel input is disabled while it's open (see -// uiMenuSetDisableBack), so it must be dismissed by picking one; CALLBACK -// then fires with the selected option index once the dialog closes. -// Option labels are passed as trailing arguments, e.g. -// CUTSCENE_MODAL_OPTIONS(title, message, callback, "Retry", "Cancel") - -// their strings are not copied by this item, so they must stay valid -// until the modal opens (string literals are fine). Like TITLE and -// MESSAGE, each option is translated if it matches a locale message ID. -#define CUTSCENE_MODAL_OPTIONS(TITLE, MESSAGE, CALLBACK, ...) \ - { \ - .type = CUTSCENE_ITEM_TYPE_MODAL, \ - .modal = { \ - .title = TITLE, .message = MESSAGE, \ - .options = (const char_t *[]){ __VA_ARGS__ }, \ - .optionCount = (uint8_t)( \ - sizeof((const char_t *[]){ __VA_ARGS__ }) / sizeof(const char_t *) \ - ), \ - .callback = CALLBACK \ - } \ - } - -// Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly one option and -// MARKER1 to jump straight to via cutsceneGoTo once it's selected - no -// callback function to write. Does not fall through to whatever follows -// this item, so MARKER1 must be scripted elsewhere in the same cutscene. -#define CUTSCENE_MODAL_OPTIONS_ONE(TITLE, MESSAGE, OPTION1, MARKER1) \ - { \ - .type = CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, \ - .modalOptionsMarkers = { \ - .title = TITLE, .message = MESSAGE, \ - .options = { OPTION1 }, \ - .markers = { MARKER1 }, \ - .optionCount = 1 \ - } \ - } - -// Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly two options, -// jumping straight to OPTION1_MARKER or OPTION2_MARKER via cutsceneGoTo -// once the corresponding option is selected - no callback function to -// write. Does not fall through to whatever follows this item, so both -// markers must be scripted elsewhere in the same cutscene. -#define CUTSCENE_MODAL_OPTIONS_TWO( \ - TITLE, MESSAGE, OPTION1, OPTION1_MARKER, OPTION2, OPTION2_MARKER \ -) \ - { \ - .type = CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, \ - .modalOptionsMarkers = { \ - .title = TITLE, .message = MESSAGE, \ - .options = { OPTION1, OPTION2 }, \ - .markers = { OPTION1_MARKER, OPTION2_MARKER }, \ - .optionCount = 2 \ - } \ - } - -// Closes the currently open modal (if any). Useful when a modal was -// opened outside of a blocking CUTSCENE_MODAL item (e.g. directly via -// uiModalOpen) and this cutscene just needs to dismiss it and continue -// on to whatever follows this item in the sequence. -#define CUTSCENE_MODAL_CLOSE() \ - { .type = CUTSCENE_ITEM_TYPE_MODAL_CLOSE } - -// Opens the on-screen keyboard and blocks the cutscene until it closes, -// then caches whatever was typed - see cutsceneSystemGetTextCache to -// read it back afterwards. __VA_ARGS__ are uikeyboardopen_t designated -// initializers, e.g. CUTSCENE_KEYBOARD(.cancel = true, .maxLength = 8). -#define CUTSCENE_KEYBOARD(...) \ - { \ - .type = CUTSCENE_ITEM_TYPE_KEYBOARD, \ - .keyboard = { .open = { __VA_ARGS__ } } \ - } - -// (Re)requests an available save device and jumps straight to -// SUCCESS_MARKER or FAILURE_MARKER once it resolves, same as a -// CUTSCENE_MODAL_OPTIONS callback - it does not fall through to -// whatever follows this item, so both markers must be scripted -// elsewhere in the same cutscene. -#define CUTSCENE_SAVE_DEVICE_CHECK(SUCCESS_MARKER, FAILURE_MARKER) \ - { \ - .type = CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK, \ - .saveDeviceCheck = { \ - .successMarker = SUCCESS_MARKER, \ - .failureMarker = FAILURE_MARKER \ - } \ - } - -// (Re)loads every save slot via saveLoadAllSlots() and jumps straight to -// SUCCESS_MARKER or FAILURE_MARKER once it resolves, same shape as -// CUTSCENE_SAVE_DEVICE_CHECK - it does not fall through to whatever -// follows this item, so both markers must be scripted elsewhere in the -// same cutscene. -#define CUTSCENE_SAVE_LOAD_ALL_SLOTS(SUCCESS_MARKER, FAILURE_MARKER) \ - { \ - .type = CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS, \ - .saveLoadAllSlots = { \ - .successMarker = SUCCESS_MARKER, \ - .failureMarker = FAILURE_MARKER \ - } \ - } - -#define CUTSCENE_ENTITY_WALK_TO(ENTITY_INDEX, X, Y, Z) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \ - .entityWalkTo = { \ - .entityIndex = ENTITY_INDEX, \ - .positions = (const worldpos_t[]){ { X, Y, Z } }, \ - .count = 1, \ - .walkAround = true \ - } \ - } - -#define CUTSCENE_ENTITY_WALK_PATH(NAME, ENTITY_INDEX, ...) \ - static const worldpos_t CUTSCENE_##NAME##_POSITIONS[] = { __VA_ARGS__ }; \ - static const cutsceneitem_t CUTSCENE_##NAME = { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \ - .entityWalkTo = { \ - .entityIndex = ENTITY_INDEX, \ - .positions = CUTSCENE_##NAME##_POSITIONS, \ - .count = sizeof(CUTSCENE_##NAME##_POSITIONS) / sizeof(worldpos_t), \ - .walkAround = true \ - } \ - } - -#define CUTSCENE_ENTITY_REMOVE(ENTITY_INDEX) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, \ - .entityRemove = { .entityIndex = ENTITY_INDEX } \ - } - -#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_ADD, \ - .entityAdd = { .entityType = TYPE, .position = { X, Y, Z } } \ - } - -#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_TURN, \ - .entityTurn = { .entityIndex = ENTITY_INDEX, .direction = DIRECTION } \ - } - -// Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by -// (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved -// from nearby terrain each frame, so ramps between the two entities are -// accounted for automatically. -#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \ - ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \ -) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, \ - .entityWalkToEntity = { \ - .entityIndex = ENTITY_INDEX, \ - .targetEntityIndex = TARGET_ENTITY_INDEX, \ - .offsetX = OFFSET_X, \ - .offsetY = OFFSET_Y \ - } \ - } - -#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, \ - .entityTeleport = { .entityIndex = ENTITY_INDEX, .target = { X, Y, Z } } \ - } - -#define CUTSCENE_FADE(FROM, TO, DURATION, EASING) \ - { \ - .type = CUTSCENE_ITEM_TYPE_FADE, \ - .fade = { .from = FROM, .to = TO, .duration = DURATION, .easing = EASING } \ - } - -#define CUTSCENE_FADE_TO_BLACK(DURATION) \ - CUTSCENE_FADE(COLOR_TRANSPARENT_BLACK, COLOR_BLACK, DURATION, EASING_LINEAR) - -#define CUTSCENE_FADE_FROM_BLACK(DURATION) \ - CUTSCENE_FADE(COLOR_BLACK, COLOR_TRANSPARENT_BLACK, DURATION, EASING_LINEAR) - -#define CUTSCENE_FADE_TO_WHITE(DURATION) \ - CUTSCENE_FADE(COLOR_TRANSPARENT_WHITE, COLOR_WHITE, DURATION, EASING_LINEAR) - -#define CUTSCENE_FADE_FROM_WHITE(DURATION) \ - CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR) - -#define CUTSCENE_EMOJI(ENTITY_INDEX, EMOJI_TYPE, DURATION) \ - { \ - .type = CUTSCENE_ITEM_TYPE_EMOJI, \ - .emoji = { \ - .entityIndex = ENTITY_INDEX, \ - .emojiType = EMOJI_TYPE, \ - .duration = DURATION \ - } \ - } - -// AMOUNT ranges 0 (no shake) to 4 (three tiles): 1 is half a tile, 2 is -// a full tile, 3 is two tiles, and 4 is three tiles. -#define CUTSCENE_SHAKE(AMOUNT, DURATION) \ - { \ - .type = CUTSCENE_ITEM_TYPE_SHAKE, \ - .shake = { .amount = AMOUNT, .duration = DURATION } \ - } - -// Waits until BATTLE.state reaches STATE. Put this BEFORE -// CUTSCENE_SET_PAUSE(CUTSCENE_PAUSE_BATTLE), not after -- pausing first -// freezes BATTLE.state wherever it already is, so it would never reach -// STATE on its own to satisfy the wait. Waiting unpaused, then pausing the -// moment it's satisfied, catches the battle right at STATE before it can -// advance further. -#define CUTSCENE_BATTLE_WAIT_STATE(STATE) \ - { \ - .type = CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE, \ - .battleWaitState = { .state = STATE } \ - } - -// Immediately queues an attack for FIGHTER_INDEX against TARGET_INDEX, -// bypassing normal player/AI selection for that fighter this round. -#define CUTSCENE_BATTLE_FORCE_ACTION(FIGHTER_INDEX, TARGET_INDEX) \ - { \ - .type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, \ - .battleForceAction = { \ - .fighterIndex = FIGHTER_INDEX, .targetIndex = TARGET_INDEX \ - } \ - } - -#define CUTSCENE_SET_PAUSE(FLAGS) \ - { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) } - -#define CUTSCENE_ITEM_GIVE(ITEM_ID, QUANTITY) \ - { \ - .type = CUTSCENE_ITEM_TYPE_ITEM_GIVE, \ - .itemGive = { .item = ITEM_ID, .quantity = QUANTITY } \ - } - -// Runs all listed items simultaneously and waits until all are done. -// Concurrent items cannot be nested inside another CUTSCENE_CONCURRENT. -#define CUTSCENE_CONCURRENT(...) \ - { \ - .type = CUTSCENE_ITEM_TYPE_CONCURRENT, \ - .concurrent = { \ - .items = (const cutsceneitem_t[]){ __VA_ARGS__ }, \ - .count = (uint8_t)( \ - sizeof((cutsceneitem_t[]){ __VA_ARGS__ }) / \ - sizeof(cutsceneitem_t) \ - ) \ - } \ - } - -#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) - -// Longhand - every audioMixerQueuePlay() parameter. See -// CUTSCENE_AUDIO_PLAY_SIMPLE/CUTSCENE_AUDIO_PLAY_LOOPED below for the -// shorthands. -#define CUTSCENE_AUDIO_PLAY( \ - FILE, CHANNEL, VOLUME, PAN, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ -) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_PLAY, \ - .audioPlay = { \ - .file = FILE, \ - .channel = CHANNEL, \ - .volume = VOLUME, \ - .pan = PAN, \ - .looping = LOOPING, \ - .loopCount = LOOP_COUNT, \ - .loopStart = LOOP_START, \ - .loopTo = LOOP_TO \ - } \ - } - -// Shorthand: play FILE on CHANNEL once, full volume, centered (0.0f, same -// as AUDIO_STREAM_CENTER), no loop. -#define CUTSCENE_AUDIO_PLAY_SIMPLE(FILE, CHANNEL) \ - CUTSCENE_AUDIO_PLAY(FILE, CHANNEL, 1.0f, 0.0f, false, 0, -1.0f, 0.0f) - -// Shorthand: play FILE on CHANNEL looping the whole clip forever, full -// volume, centered (0.0f, same as AUDIO_STREAM_CENTER). -#define CUTSCENE_AUDIO_PLAY_LOOPED(FILE, CHANNEL) \ - CUTSCENE_AUDIO_PLAY(FILE, CHANNEL, 1.0f, 0.0f, true, 0, -1.0f, 0.0f) - -#define CUTSCENE_AUDIO_STOP(CHANNEL) \ - { .type = CUTSCENE_ITEM_TYPE_AUDIO_STOP, .audioStopChannel = CHANNEL } - -#define CUTSCENE_AUDIO_PAUSE(CHANNEL) \ - { .type = CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, .audioPauseChannel = CHANNEL } - -#define CUTSCENE_AUDIO_RESUME(CHANNEL) \ - { .type = CUTSCENE_ITEM_TYPE_AUDIO_RESUME, .audioResumeChannel = CHANNEL } - -// Longhand - starts CHANNEL fading from FROM to TO over DURATION seconds, -// eased by EASING. Doesn't wait for it - pair with -// CUTSCENE_AUDIO_FADE_WAIT(CHANNEL) to block until it finishes. See -// CUTSCENE_AUDIO_FADE_OUT/CUTSCENE_AUDIO_FADE_IN below for the shorthands; -// use this directly for a custom range, e.g. -// CUTSCENE_AUDIO_FADE(CHANNEL, 1.0f, 0.3f, 5.0f, EASING_LINEAR) to fade -// down to "playing quietly" rather than silent. -#define CUTSCENE_AUDIO_FADE(CHANNEL, FROM, TO, DURATION, EASING) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_FADE, \ - .audioFade = { \ - .channel = CHANNEL, \ - .from = FROM, \ - .to = TO, \ - .duration = DURATION, \ - .easing = EASING \ - } \ - } - -// Shorthand: fade CHANNEL from full volume to silent over DURATION seconds. -#define CUTSCENE_AUDIO_FADE_OUT(CHANNEL, DURATION) \ - CUTSCENE_AUDIO_FADE(CHANNEL, 1.0f, 0.0f, DURATION, EASING_LINEAR) - -// Shorthand: fade CHANNEL from silent to full volume over DURATION seconds. -#define CUTSCENE_AUDIO_FADE_IN(CHANNEL, DURATION) \ - CUTSCENE_AUDIO_FADE(CHANNEL, 0.0f, 1.0f, DURATION, EASING_LINEAR) - -// Waits until CHANNEL's fade (started elsewhere by CUTSCENE_AUDIO_FADE) -// has finished - a no-op wait if CHANNEL isn't fading. -#define CUTSCENE_AUDIO_FADE_WAIT(CHANNEL) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT, \ - .audioFadeWaitChannel = CHANNEL \ - } - -#define CUTSCENE_AUDIO_SET_PAN(CHANNEL, PAN) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, \ - .audioSetPan = { .channel = CHANNEL, .pan = PAN } \ - } - -#define CUTSCENE_AUDIO_SET_LOOP( \ - CHANNEL, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ -) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, \ - .audioSetLoop = { \ - .channel = CHANNEL, \ - .looping = LOOPING, \ - .loopCount = LOOP_COUNT, \ - .loopStart = LOOP_START, \ - .loopTo = LOOP_TO \ - } \ - } - -// Combined form of CUTSCENE_AUDIO_SET_PAN + CUTSCENE_AUDIO_SET_LOOP, for -// setting both at once rather than as two separate items. -#define CUTSCENE_AUDIO_SET( \ - CHANNEL, PAN, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ -) \ - { \ - .type = CUTSCENE_ITEM_TYPE_AUDIO_SET, \ - .audioSet = { \ - .channel = CHANNEL, \ - .pan = PAN, \ - .looping = LOOPING, \ - .loopCount = LOOP_COUNT, \ - .loopStart = LOOP_START, \ - .loopTo = LOOP_TO \ - } \ - } diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h index 09631ae3..e26ae42d 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h @@ -18,6 +18,50 @@ typedef struct { easingtype_t easing; } cutsceneaudiofade_t; +/** + * Longhand - starts CHANNEL fading from FROM to TO over DURATION seconds, + * eased by EASING. Doesn't wait for it - pair with + * CUTSCENE_AUDIO_FADE_WAIT(CHANNEL) to block until it finishes. See + * CUTSCENE_AUDIO_FADE_OUT/CUTSCENE_AUDIO_FADE_IN below for the + * shorthands; use this directly for a custom range, e.g. + * CUTSCENE_AUDIO_FADE(CHANNEL, 1.0f, 0.3f, 5.0f, EASING_LINEAR) to fade + * down to "playing quietly" rather than silent. + * + * @param CHANNEL audiomixerchannel_t to fade. + * @param FROM Starting volume (0.0-1.0). + * @param TO Ending volume (0.0-1.0). + * @param DURATION Duration in seconds. + * @param EASING easingtype_t to apply. + */ +#define CUTSCENE_AUDIO_FADE(CHANNEL, FROM, TO, DURATION, EASING) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_FADE, audioFade, { \ + .channel = CHANNEL, \ + .from = FROM, \ + .to = TO, \ + .duration = DURATION, \ + .easing = EASING \ + }) + +/** + * Shorthand: fade CHANNEL from full volume to silent over DURATION + * seconds. + * + * @param CHANNEL audiomixerchannel_t to fade. + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_AUDIO_FADE_OUT(CHANNEL, DURATION) \ + CUTSCENE_AUDIO_FADE(CHANNEL, 1.0f, 0.0f, DURATION, EASING_LINEAR) + +/** + * Shorthand: fade CHANNEL from silent to full volume over DURATION + * seconds. + * + * @param CHANNEL audiomixerchannel_t to fade. + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_AUDIO_FADE_IN(CHANNEL, DURATION) \ + CUTSCENE_AUDIO_FADE(CHANNEL, 0.0f, 1.0f, DURATION, EASING_LINEAR) + /** * Starts an audio fade step (starts the fade via audioMixerFadeTo() - * doesn't wait for it; pair with CUTSCENE_AUDIO_FADE_WAIT to block on it). diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h index 739db057..5a2cf04f 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h @@ -9,6 +9,15 @@ #include "audio/mixer/audiomixerchannel.h" #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Waits until CHANNEL's fade (started elsewhere by CUTSCENE_AUDIO_FADE) + * has finished - a no-op wait if CHANNEL isn't fading. + * + * @param CHANNEL audiomixerchannel_t to wait on. + */ +#define CUTSCENE_AUDIO_FADE_WAIT(CHANNEL) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT, audioFadeWaitChannel, CHANNEL) + /** * Updates an audio fade-wait step, completing once the watched channel's * fade (started by a CUTSCENE_AUDIO_FADE elsewhere) finishes. Has no Start diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h index 335e363d..64c28f4b 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h @@ -9,6 +9,14 @@ #include "audio/mixer/audiomixerchannel.h" #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Pauses CHANNEL immediately. + * + * @param CHANNEL audiomixerchannel_t to pause. + */ +#define CUTSCENE_AUDIO_PAUSE(CHANNEL) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, audioPauseChannel, CHANNEL) + /** * Starts an audio pause step (pauses the channel immediately). * diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h index 007f913b..df193c1d 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h @@ -20,6 +20,55 @@ typedef struct { float_t loopTo; } cutsceneaudioplay_t; +/** + * Longhand - queues a sound via audioMixerQueuePlay() (see its own + * comment on when it actually starts). See CUTSCENE_AUDIO_PLAY_SIMPLE/ + * CUTSCENE_AUDIO_PLAY_LOOPED below for the shorthands. + * + * @param FILE Audio file path. + * @param CHANNEL audiomixerchannel_t to play on. + * @param VOLUME Playback volume (0.0-1.0). + * @param PAN Stereo pan (-1.0 left to 1.0 right, 0.0 centered). + * @param LOOPING Whether the clip loops. + * @param LOOP_COUNT Number of loop repeats (0 = infinite, if LOOPING). + * @param LOOP_START Loop region start in seconds, or -1.0f for the clip + * start. + * @param LOOP_TO Loop region end/return point in seconds. + */ +#define CUTSCENE_AUDIO_PLAY( \ + FILE, CHANNEL, VOLUME, PAN, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ +) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_PLAY, audioPlay, { \ + .file = FILE, \ + .channel = CHANNEL, \ + .volume = VOLUME, \ + .pan = PAN, \ + .looping = LOOPING, \ + .loopCount = LOOP_COUNT, \ + .loopStart = LOOP_START, \ + .loopTo = LOOP_TO \ + }) + +/** + * Shorthand: play FILE on CHANNEL once, full volume, centered (0.0f, same + * as AUDIO_STREAM_CENTER), no loop. + * + * @param FILE Audio file path. + * @param CHANNEL audiomixerchannel_t to play on. + */ +#define CUTSCENE_AUDIO_PLAY_SIMPLE(FILE, CHANNEL) \ + CUTSCENE_AUDIO_PLAY(FILE, CHANNEL, 1.0f, 0.0f, false, 0, -1.0f, 0.0f) + +/** + * Shorthand: play FILE on CHANNEL looping the whole clip forever, full + * volume, centered (0.0f, same as AUDIO_STREAM_CENTER). + * + * @param FILE Audio file path. + * @param CHANNEL audiomixerchannel_t to play on. + */ +#define CUTSCENE_AUDIO_PLAY_LOOPED(FILE, CHANNEL) \ + CUTSCENE_AUDIO_PLAY(FILE, CHANNEL, 1.0f, 0.0f, true, 0, -1.0f, 0.0f) + /** * Starts an audio play step (queues the sound via audioMixerQueuePlay() - * see its own comment on when it actually starts). diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h index e04ab810..551c5837 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h @@ -9,6 +9,14 @@ #include "audio/mixer/audiomixerchannel.h" #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Resumes CHANNEL immediately. + * + * @param CHANNEL audiomixerchannel_t to resume. + */ +#define CUTSCENE_AUDIO_RESUME(CHANNEL) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_RESUME, audioResumeChannel, CHANNEL) + /** * Starts an audio resume step (resumes the channel immediately). * diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h index 4ae69c3d..3f13fca1 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h @@ -18,6 +18,30 @@ typedef struct { float_t loopTo; } cutsceneaudioset_t; +/** + * Combined form of CUTSCENE_AUDIO_SET_PAN + CUTSCENE_AUDIO_SET_LOOP, for + * setting both at once rather than as two separate items. + * + * @param CHANNEL audiomixerchannel_t to adjust. + * @param PAN Stereo pan (-1.0 left to 1.0 right, 0.0 centered). + * @param LOOPING Whether the clip loops. + * @param LOOP_COUNT Number of loop repeats (0 = infinite, if LOOPING). + * @param LOOP_START Loop region start in seconds, or -1.0f for the clip + * start. + * @param LOOP_TO Loop region end/return point in seconds. + */ +#define CUTSCENE_AUDIO_SET( \ + CHANNEL, PAN, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ +) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_SET, audioSet, { \ + .channel = CHANNEL, \ + .pan = PAN, \ + .looping = LOOPING, \ + .loopCount = LOOP_COUNT, \ + .loopStart = LOOP_START, \ + .loopTo = LOOP_TO \ + }) + /** * Starts an audio set step - applies pan and looping behaviour together in * one item (via audioMixerSetPan()/audioMixerSetLoop()), for when you want diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h index 518bb9da..689fb3e0 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h @@ -17,6 +17,27 @@ typedef struct { float_t loopTo; } cutsceneaudiosetloop_t; +/** + * Applies new looping behaviour immediately, via audioMixerSetLoop(). + * + * @param CHANNEL audiomixerchannel_t to adjust. + * @param LOOPING Whether the clip loops. + * @param LOOP_COUNT Number of loop repeats (0 = infinite, if LOOPING). + * @param LOOP_START Loop region start in seconds, or -1.0f for the clip + * start. + * @param LOOP_TO Loop region end/return point in seconds. + */ +#define CUTSCENE_AUDIO_SET_LOOP( \ + CHANNEL, LOOPING, LOOP_COUNT, LOOP_START, LOOP_TO \ +) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, audioSetLoop, { \ + .channel = CHANNEL, \ + .looping = LOOPING, \ + .loopCount = LOOP_COUNT, \ + .loopStart = LOOP_START, \ + .loopTo = LOOP_TO \ + }) + /** * Starts an audio set-loop step (applies the new looping behaviour * immediately, via audioMixerSetLoop()). diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h index 4599c415..72abf8fd 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h @@ -14,6 +14,17 @@ typedef struct { float_t pan; } cutsceneaudiosetpan_t; +/** + * Applies a new pan immediately, via audioMixerSetPan(). + * + * @param CHANNEL audiomixerchannel_t to adjust. + * @param PAN Stereo pan (-1.0 left to 1.0 right, 0.0 centered). + */ +#define CUTSCENE_AUDIO_SET_PAN(CHANNEL, PAN) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, audioSetPan, { \ + .channel = CHANNEL, .pan = PAN \ + }) + /** * Starts an audio set-pan step (applies the new pan immediately, via * audioMixerSetPan()). diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h index ff366870..a5515c63 100644 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h +++ b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h @@ -9,6 +9,14 @@ #include "audio/mixer/audiomixerchannel.h" #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Stops CHANNEL immediately. + * + * @param CHANNEL audiomixerchannel_t to stop. + */ +#define CUTSCENE_AUDIO_STOP(CHANNEL) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_AUDIO_STOP, audioStopChannel, CHANNEL) + /** * Starts an audio stop step (stops the channel immediately). * diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h index 52bebc95..033cb1df 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattleforceaction.h @@ -14,6 +14,18 @@ typedef struct { uint8_t targetIndex; } cutscenebattleforceaction_t; +/** + * Immediately queues an attack for FIGHTER_INDEX against TARGET_INDEX, + * bypassing normal player/AI selection for that fighter this round. + * + * @param FIGHTER_INDEX Attacking fighter's battle index. + * @param TARGET_INDEX Target fighter's battle index. + */ +#define CUTSCENE_BATTLE_FORCE_ACTION(FIGHTER_INDEX, TARGET_INDEX) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, battleForceAction, { \ + .fighterIndex = FIGHTER_INDEX, .targetIndex = TARGET_INDEX \ + }) + /** * Starts a battle force-action step: immediately queues an attack for the * given fighter against the given target, bypassing normal player/AI diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h index e4b9d837..1e6618fc 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitstate.h @@ -13,6 +13,21 @@ typedef struct { battlestate_t state; } cutscenebattlewaitstate_t; +/** + * Waits until BATTLE.state reaches STATE. Put this BEFORE + * CUTSCENE_SET_PAUSE(CUTSCENE_PAUSE_BATTLE), not after -- pausing first + * freezes BATTLE.state wherever it already is, so it would never reach + * STATE on its own to satisfy the wait. Waiting unpaused, then pausing + * the moment it's satisfied, catches the battle right at STATE before it + * can advance further. + * + * @param STATE battlestate_t to wait for. + */ +#define CUTSCENE_BATTLE_WAIT_STATE(STATE) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE, battleWaitState, { \ + .state = STATE \ + }) + /** * Updates a battle wait-state step, completing once BATTLE.state reaches * the watched state. Has no Start callback -- there's nothing to do until diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h index 71b0ba1e..591428f3 100644 --- a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h +++ b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h @@ -13,6 +13,22 @@ /** Maximum number of items that may run inside a CUTSCENE_CONCURRENT. */ #define CUTSCENE_CONCURRENT_MAX 8 +/** + * Runs all listed items simultaneously and waits until all are done. + * Concurrent items cannot be nested inside another CUTSCENE_CONCURRENT. + * + * @param ... Child cutsceneitem_t entries (e.g. CUTSCENE_WAIT(...), + * CUTSCENE_ENTITY_WALK_TO(...)) to run at the same time. + */ +#define CUTSCENE_CONCURRENT(...) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_CONCURRENT, concurrent, { \ + .items = (const cutsceneitem_t[]){ __VA_ARGS__ }, \ + .count = (uint8_t)( \ + sizeof((cutsceneitem_t[]){ __VA_ARGS__ }) / \ + sizeof(cutsceneitem_t) \ + ) \ + }) + /** * Static (const) data for a concurrent cutscene item. */ diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h b/src/dusk/rpg/cutscene/item/control/cutsceneidle.h index d819084e..f98ecc9e 100644 --- a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h +++ b/src/dusk/rpg/cutscene/item/control/cutsceneidle.h @@ -8,6 +8,14 @@ #pragma once #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Blocks the cutscene here indefinitely - it never completes on its own, + * only cutsceneGoTo (called externally, e.g. from a UI callback) can move + * execution past it. + */ +#define CUTSCENE_IDLE() \ + { .type = CUTSCENE_ITEM_TYPE_IDLE } + /** * Updates an idle item. Never completes on its own - the cutscene blocks * here indefinitely until something external (e.g. a UI callback) calls diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h index 3cb8faf5..4ea74dba 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h @@ -12,6 +12,17 @@ typedef struct { const char_t *name; } cutscenemarker_t; +/** + * A named, otherwise no-op position in the item list that cutsceneGoTo + * can jump execution straight to. + * + * @param NAME Marker name, matched with stringEquals (not pointer + * identity) - safe to use separate string literals with the same + * contents at the marker and at each call site. + */ +#define CUTSCENE_MARKER(NAME) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MARKER, marker, { .name = NAME }) + /** * Starts a marker item. A marker does nothing on its own - it exists * purely as a named position for cutsceneGoTo to jump to. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h b/src/dusk/rpg/cutscene/item/control/cutscenerestart.h index a1e05dd4..1959506a 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenerestart.h @@ -8,6 +8,13 @@ #pragma once #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Restarts the currently running cutscene from its first item, + * preserving whatever interact/interacted entities triggered it. + */ +#define CUTSCENE_RESTART() \ + { .type = CUTSCENE_ITEM_TYPE_RESTART } + /** * Starts a restart item (restarts the currently running cutscene from * its first item via cutsceneRestart). diff --git a/src/dusk/rpg/cutscene/item/control/cutscenescene.h b/src/dusk/rpg/cutscene/item/control/cutscenescene.h index bde0493d..0cd0de69 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenescene.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenescene.h @@ -13,6 +13,17 @@ typedef struct { scenetype_t type; } cutscenescene_t; +/** + * Requests a switch to a different SCENE_TYPE via sceneSet, then + * immediately continues on to whatever follows this item - the switch + * itself doesn't happen until the next sceneUpdate() tick, so it does + * not take effect this frame. + * + * @param TYPE Target scenetype_t to switch to. + */ +#define CUTSCENE_SCENE(TYPE) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_SCENE, sceneChange, { .type = TYPE }) + /** * Starts a scene item (requests a switch to the given scene via * sceneSet). The switch itself doesn't happen until the next diff --git a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h index ad75a046..d17416ad 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h @@ -9,6 +9,14 @@ #include "rpg/cutscene/cutscenepause.h" #include "rpg/cutscene/item/cutsceneitembase.h" +/** + * Applies the given pause flags immediately. + * + * @param FLAGS CUTSCENE_PAUSE_* flags, OR'd together. + */ +#define CUTSCENE_SET_PAUSE(FLAGS) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_SET_PAUSE, setPause, (FLAGS)) + /** * Starts a set-pause item (applies the new pause flags immediately). * diff --git a/src/dusk/rpg/cutscene/item/control/cutscenewait.h b/src/dusk/rpg/cutscene/item/control/cutscenewait.h index 7ac03793..528e9a3a 100644 --- a/src/dusk/rpg/cutscene/item/control/cutscenewait.h +++ b/src/dusk/rpg/cutscene/item/control/cutscenewait.h @@ -11,6 +11,14 @@ typedef float_t cutscenewait_t; typedef float_t cutscenewaitdata_t; +/** + * Waits WAIT seconds before continuing. + * + * @param WAIT Duration in seconds. + */ +#define CUTSCENE_WAIT(WAIT) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_WAIT, wait, WAIT) + /** * Starts a wait item (stores the duration in data). * diff --git a/src/dusk/rpg/cutscene/item/cutscenecallback.h b/src/dusk/rpg/cutscene/item/cutscenecallback.h index 41029607..c468b4b5 100644 --- a/src/dusk/rpg/cutscene/item/cutscenecallback.h +++ b/src/dusk/rpg/cutscene/item/cutscenecallback.h @@ -10,6 +10,15 @@ typedef void (*cutscenecallback_t)(void *userData); +/** + * Invokes CALLBACK immediately, then continues on to whatever follows + * this item. + * + * @param CALLBACK A cutscenecallback_t function pointer. + */ +#define CUTSCENE_CALLBACK(CALLBACK) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_CALLBACK, callback, CALLBACK) + /** * Starts a callback item (invokes the callback immediately). * diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index 2c5af59e..069df684 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -8,6 +8,7 @@ #pragma once #include "error/error.h" #include "yyjson.h" +#include "cutsceneitembase.h" #include "cutscenecallback.h" #include "cutsceneprint.h" #include "control/cutscenewait.h" @@ -70,60 +71,6 @@ typedef struct { const char_t *name; } cutscenecutsceneref_t; -typedef enum { - CUTSCENE_ITEM_TYPE_NULL, - - CUTSCENE_ITEM_TYPE_TEXT, - CUTSCENE_ITEM_TYPE_TEXT_MINI, - CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, - CUTSCENE_ITEM_TYPE_CALLBACK, - CUTSCENE_ITEM_TYPE_WAIT, - CUTSCENE_ITEM_TYPE_CUTSCENE, - CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, - CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, - CUTSCENE_ITEM_TYPE_FADE, - CUTSCENE_ITEM_TYPE_SET_PAUSE, - CUTSCENE_ITEM_TYPE_CONCURRENT, - CUTSCENE_ITEM_TYPE_ITEM_GIVE, - CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, - CUTSCENE_ITEM_TYPE_ENTITY_ADD, - CUTSCENE_ITEM_TYPE_ENTITY_TURN, - 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, - CUTSCENE_ITEM_TYPE_START_BATTLE, - CUTSCENE_ITEM_TYPE_EMOJI, - CUTSCENE_ITEM_TYPE_SHAKE, - CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE, - CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, - CUTSCENE_ITEM_TYPE_MODAL, - CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, - CUTSCENE_ITEM_TYPE_MODAL_CLOSE, - CUTSCENE_ITEM_TYPE_PRINT, - CUTSCENE_ITEM_TYPE_MARKER, - CUTSCENE_ITEM_TYPE_RESTART, - CUTSCENE_ITEM_TYPE_QUIT_GAME, - CUTSCENE_ITEM_TYPE_SCENE, - CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK, - CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS, - CUTSCENE_ITEM_TYPE_KEYBOARD, - CUTSCENE_ITEM_TYPE_AUDIO_PLAY, - CUTSCENE_ITEM_TYPE_AUDIO_STOP, - CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, - CUTSCENE_ITEM_TYPE_AUDIO_RESUME, - CUTSCENE_ITEM_TYPE_AUDIO_FADE, - CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT, - CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, - CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, - CUTSCENE_ITEM_TYPE_AUDIO_SET, - CUTSCENE_ITEM_TYPE_IDLE, - CUTSCENE_ITEM_TYPE_UI_SHOW, - CUTSCENE_ITEM_TYPE_REGULAR_BATTLE, - - CUTSCENE_ITEM_TYPE_COUNT -} cutsceneitemtype_t; - struct cutsceneitem_s { cutsceneitemtype_t type; diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.h b/src/dusk/rpg/cutscene/item/cutsceneitembase.h index 2a1c6556..15741311 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitembase.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitembase.h @@ -13,6 +13,75 @@ typedef struct cutsceneitem_s cutsceneitem_t; typedef union cutsceneitemdata_u cutsceneitemdata_t; +typedef enum { + CUTSCENE_ITEM_TYPE_NULL, + + CUTSCENE_ITEM_TYPE_TEXT, + CUTSCENE_ITEM_TYPE_TEXT_MINI, + CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, + CUTSCENE_ITEM_TYPE_CALLBACK, + CUTSCENE_ITEM_TYPE_WAIT, + CUTSCENE_ITEM_TYPE_CUTSCENE, + CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, + CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, + CUTSCENE_ITEM_TYPE_FADE, + CUTSCENE_ITEM_TYPE_SET_PAUSE, + CUTSCENE_ITEM_TYPE_CONCURRENT, + CUTSCENE_ITEM_TYPE_ITEM_GIVE, + CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, + CUTSCENE_ITEM_TYPE_ENTITY_ADD, + CUTSCENE_ITEM_TYPE_ENTITY_TURN, + 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, + CUTSCENE_ITEM_TYPE_START_BATTLE, + CUTSCENE_ITEM_TYPE_EMOJI, + CUTSCENE_ITEM_TYPE_SHAKE, + CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE, + CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, + CUTSCENE_ITEM_TYPE_MODAL, + CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, + CUTSCENE_ITEM_TYPE_MODAL_CLOSE, + CUTSCENE_ITEM_TYPE_PRINT, + CUTSCENE_ITEM_TYPE_MARKER, + CUTSCENE_ITEM_TYPE_RESTART, + CUTSCENE_ITEM_TYPE_QUIT_GAME, + CUTSCENE_ITEM_TYPE_SCENE, + CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK, + CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS, + CUTSCENE_ITEM_TYPE_KEYBOARD, + CUTSCENE_ITEM_TYPE_AUDIO_PLAY, + CUTSCENE_ITEM_TYPE_AUDIO_STOP, + CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, + CUTSCENE_ITEM_TYPE_AUDIO_RESUME, + CUTSCENE_ITEM_TYPE_AUDIO_FADE, + CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT, + CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, + CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, + CUTSCENE_ITEM_TYPE_AUDIO_SET, + CUTSCENE_ITEM_TYPE_IDLE, + CUTSCENE_ITEM_TYPE_UI_SHOW, + CUTSCENE_ITEM_TYPE_REGULAR_BATTLE, + + CUTSCENE_ITEM_TYPE_COUNT +} cutsceneitemtype_t; + +/** + * Shorthand for a cutsceneitem_t literal - used by each item type's own + * authoring macro (e.g. CUTSCENE_WAIT in cutscenewait.h) to fill in .type + * and its union member together. + * + * @param TYPE The item's CUTSCENE_ITEM_TYPE_* enum value. + * @param UNION_NAME Name of the cutsceneitem_t union member to fill. + * @param ... The union member's value. Passed through __VA_ARGS__ (rather + * than a single named parameter) so it may itself be a brace initializer + * with top-level commas, e.g. + * CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_FADE, fade, { .from = A, .to = B }). + */ +#define CUTSCENE_ITEM(TYPE, UNION_NAME, ...) \ + { .type = TYPE, .UNION_NAME = __VA_ARGS__ } + /** * Shared no-op Load for item types with no JSON fields of their own to * parse (IDLE, RESTART, MODAL_CLOSE) - always succeeds without touching diff --git a/src/dusk/rpg/cutscene/item/cutsceneprint.h b/src/dusk/rpg/cutscene/item/cutsceneprint.h index 9eaef022..a60aabcd 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneprint.h +++ b/src/dusk/rpg/cutscene/item/cutsceneprint.h @@ -14,6 +14,14 @@ typedef struct { char_t text[CUTSCENE_PRINT_MAX_CHARS]; } cutsceneprint_t; +/** + * Prints TEXT to the console. + * + * @param TEXT Text to print. + */ +#define CUTSCENE_PRINT(TEXT) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_PRINT, print, { .text = TEXT }) + /** * Starts a print item (prints the item's text to the console). * diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h index 4bece21c..dc193d16 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h @@ -15,6 +15,19 @@ typedef struct { worldpos_t position; } cutsceneentityadd_t; +/** + * Spawns a new entity into the world immediately. + * + * @param TYPE Entity type to spawn. + * @param X Spawn world X. + * @param Y Spawn world Y. + * @param Z Spawn world Z. + */ +#define CUTSCENE_ENTITY_ADD(TYPE, X, Y, Z) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_ADD, entityAdd, { \ + .entityType = TYPE, .position = { X, Y, Z } \ + }) + /** * Starts an entity add step (spawns the entity into the world immediately). * diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h index beb7187a..88c3b8bd 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h @@ -12,6 +12,16 @@ typedef struct { uint8_t entityIndex; } cutsceneentityremove_t; +/** + * Removes an entity from the world immediately. + * + * @param ENTITY_INDEX Entity index to remove. + */ +#define CUTSCENE_ENTITY_REMOVE(ENTITY_INDEX) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, entityRemove, { \ + .entityIndex = ENTITY_INDEX \ + }) + /** * Starts an entity remove step (removes the entity from the world immediately). * diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h index 9d474cea..fc724023 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h @@ -14,6 +14,19 @@ typedef struct { worldpos_t target; } cutsceneentityteleport_t; +/** + * Teleports an entity to a world position immediately. + * + * @param ENTITY_INDEX Entity index to teleport. + * @param X Target world X. + * @param Y Target world Y. + * @param Z Target world Z. + */ +#define CUTSCENE_ENTITY_TELEPORT(ENTITY_INDEX, X, Y, Z) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, entityTeleport, { \ + .entityIndex = ENTITY_INDEX, .target = { X, Y, Z } \ + }) + /** * Starts an entity teleport item (teleports the entity immediately). * diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h index 4d7a6213..57a65839 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h @@ -14,6 +14,17 @@ typedef struct { entitydir_t direction; } cutsceneentityturn_t; +/** + * Turns an entity to face a direction. + * + * @param ENTITY_INDEX Entity index to turn. + * @param DIRECTION Target entitydir_t to face. + */ +#define CUTSCENE_ENTITY_TURN(ENTITY_INDEX, DIRECTION) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_TURN, entityTurn, { \ + .entityIndex = ENTITY_INDEX, .direction = DIRECTION \ + }) + /** * Starts an entity turn step. The turn itself is driven from Update, since * the entity may still be finishing a previous action. diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h index 705eeb66..25a2aac8 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h @@ -20,6 +20,45 @@ typedef struct { uint8_t currentIndex; } cutsceneentitywalktodata_t; +/** + * Walks an entity to a single world position, navigating around + * obstacles. + * + * @param ENTITY_INDEX Entity index to move. + * @param X Target world X. + * @param Y Target world Y. + * @param Z Target world Z. + */ +#define CUTSCENE_ENTITY_WALK_TO(ENTITY_INDEX, X, Y, Z) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, entityWalkTo, { \ + .entityIndex = ENTITY_INDEX, \ + .positions = (const worldpos_t[]){ { X, Y, Z } }, \ + .count = 1, \ + .walkAround = true \ + }) + +/** + * Declares a standalone, named cutsceneitem_t (not a list entry) that + * walks an entity through a fixed sequence of waypoints. + * + * @param NAME Suffix for the generated CUTSCENE_##NAME item/positions + * statics. + * @param ENTITY_INDEX Entity index to move. + * @param ... One or more worldpos_t-shaped waypoint initializers, e.g. + * { X, Y, Z }, { X2, Y2, Z2 }. + */ +#define CUTSCENE_ENTITY_WALK_PATH(NAME, ENTITY_INDEX, ...) \ + static const worldpos_t CUTSCENE_##NAME##_POSITIONS[] = { __VA_ARGS__ }; \ + static const cutsceneitem_t CUTSCENE_##NAME = { \ + .type = CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, \ + .entityWalkTo = { \ + .entityIndex = ENTITY_INDEX, \ + .positions = CUTSCENE_##NAME##_POSITIONS, \ + .count = sizeof(CUTSCENE_##NAME##_POSITIONS) / sizeof(worldpos_t), \ + .walkAround = true \ + } \ + }; + /** * Starts an entity walk-to item (resets the waypoint index). * diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h index 2d19a4bb..114fe4d8 100644 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h +++ b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h @@ -16,6 +16,27 @@ typedef struct { worldunit_t offsetY; } cutsceneentitywalktoentity_t; +/** + * Walks ENTITY_INDEX to stand beside TARGET_ENTITY_INDEX, offset by + * (OFFSET_X, OFFSET_Y) on the 2D plane. The destination Z is resolved + * from nearby terrain each frame, so ramps between the two entities are + * accounted for automatically. + * + * @param ENTITY_INDEX Entity index to move. + * @param TARGET_ENTITY_INDEX Entity index to walk toward. + * @param OFFSET_X X offset from the target entity's position. + * @param OFFSET_Y Y offset from the target entity's position. + */ +#define CUTSCENE_ENTITY_WALK_TO_ENTITY( \ + ENTITY_INDEX, TARGET_ENTITY_INDEX, OFFSET_X, OFFSET_Y \ +) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, entityWalkToEntity, { \ + .entityIndex = ENTITY_INDEX, \ + .targetEntityIndex = TARGET_ENTITY_INDEX, \ + .offsetX = OFFSET_X, \ + .offsetY = OFFSET_Y \ + }) + /** * Starts an entity walk-to-entity item. No setup is needed, the destination * is recomputed from the target's live position every Update. diff --git a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h index 538d69e2..e5587a8a 100644 --- a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h +++ b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h @@ -14,6 +14,17 @@ typedef struct { uint8_t quantity; } cutsceneitemgive_t; +/** + * Adds an item to the player's backpack immediately. + * + * @param ITEM_ID Item ID to give. + * @param QUANTITY Quantity to give. + */ +#define CUTSCENE_ITEM_GIVE(ITEM_ID, QUANTITY) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_ITEM_GIVE, itemGive, { \ + .item = ITEM_ID, .quantity = QUANTITY \ + }) + /** * Starts a give-item step (adds the item to the player's backpack immediately). * diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h index 14cb13db..d6f61641 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h @@ -8,6 +8,8 @@ #pragma once #include "rpg/cutscene/item/cutsceneitembase.h" #include "rpg/overworld/maparea.h" +#include "rpg/cutscene/item/maparea/cutscenemapareawait.h" +#include "rpg/cutscene/item/maparea/cutscenemaparearemove.h" typedef struct { worldpos_t min; @@ -17,6 +19,55 @@ typedef struct { uint8_t trigger; } cutscenemapareaadd_t; +/** + * Adds a map area immediately, storing its ID in + * CUTSCENE_SYSTEM.areaLastCreated. + * + * @param MIN_X Area bounding box minimum world X. + * @param MIN_Y Area bounding box minimum world Y. + * @param MIN_Z Area bounding box minimum world Z. + * @param MAX_X Area bounding box maximum world X. + * @param MAX_Y Area bounding box maximum world Y. + * @param MAX_Z Area bounding box maximum world Z. + * @param CALLBACK mapareacallback_t to invoke when the area is triggered. + * @param NOTIFY Notify count/threshold passed to the area. + * @param TRIGGER Trigger count/threshold passed to the area. + */ +#define CUTSCENE_MAP_AREA_ADD( \ + MIN_X, MIN_Y, MIN_Z, MAX_X, MAX_Y, MAX_Z, CALLBACK, NOTIFY, TRIGGER \ +) \ + CUTSCENE_ITEM(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 \ + }) + +/** + * 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. + * + * @param MIN_X Area bounding box minimum world X. + * @param MIN_Y Area bounding box minimum world Y. + * @param MIN_Z Area bounding box minimum world Z. + * @param MAX_X Area bounding box maximum world X. + * @param MAX_Y Area bounding box maximum world Y. + * @param MAX_Z Area bounding box maximum world Z. + * @param NOTIFY Notify count/threshold passed to the area. + * @param TRIGGER Trigger count/threshold passed to the area. + */ +#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) + /** * Starts a map area add step (adds the area immediately, storing its ID * in CUTSCENE_SYSTEM.areaLastCreated). diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h index bca46048..45d38599 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h @@ -12,6 +12,17 @@ typedef struct { uint8_t areaId; } cutscenemaparearemove_t; +/** + * Removes a map area immediately. + * + * @param AREA_ID Area ID to remove, or CUTSCENE_AREA_LAST_CREATED in + * place of a literal area ID. + */ +#define CUTSCENE_MAP_AREA_REMOVE(AREA_ID) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, mapAreaRemove, { \ + .areaId = AREA_ID \ + }) + /** * Starts a map area remove step (removes the area immediately). Accepts * CUTSCENE_AREA_LAST_CREATED in place of a literal area ID. diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h index e443fdcb..575b023c 100644 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h +++ b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h @@ -20,6 +20,20 @@ typedef struct { uint32_t baseline[CUTSCENE_MAP_AREA_WAIT_MAX]; } cutscenemapareawaitdata_t; +/** + * Waits until any one of the given map area IDs has its callback invoked. + * + * @param ... One or more area IDs to watch. Accepts + * CUTSCENE_AREA_LAST_CREATED in place of a literal area ID. + */ +#define CUTSCENE_MAP_AREA_WAIT(...) \ + CUTSCENE_ITEM(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) \ + ) \ + }) + /** * Starts a map area wait step, snapshotting each watched area's current * trigger count. diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h index 895f5291..24c21c42 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h @@ -13,6 +13,22 @@ typedef struct { const char_t *failureMarker; } cutscenesavedevicecheck_t; +/** + * (Re)requests an available save device and jumps straight to + * SUCCESS_MARKER or FAILURE_MARKER once it resolves, same as a + * CUTSCENE_MODAL_OPTIONS callback - it does not fall through to + * whatever follows this item, so both markers must be scripted + * elsewhere in the same cutscene. + * + * @param SUCCESS_MARKER Marker to jump to if a device is available. + * @param FAILURE_MARKER Marker to jump to if no device is available. + */ +#define CUTSCENE_SAVE_DEVICE_CHECK(SUCCESS_MARKER, FAILURE_MARKER) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK, saveDeviceCheck, { \ + .successMarker = SUCCESS_MARKER, \ + .failureMarker = FAILURE_MARKER \ + }) + /** * Starts a save-device-check item: (re)requests an available save * device via saveFindAvailableDevice. Never completes on its own - see diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h index 1208c05d..e1aeac2e 100644 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h +++ b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h @@ -13,6 +13,22 @@ typedef struct { const char_t *failureMarker; } cutscenesaveloadallslots_t; +/** + * (Re)loads every save slot via saveLoadAllSlots() and jumps straight to + * SUCCESS_MARKER or FAILURE_MARKER once it resolves, same shape as + * CUTSCENE_SAVE_DEVICE_CHECK - it does not fall through to whatever + * follows this item, so both markers must be scripted elsewhere in the + * same cutscene. + * + * @param SUCCESS_MARKER Marker to jump to on success. + * @param FAILURE_MARKER Marker to jump to on failure. + */ +#define CUTSCENE_SAVE_LOAD_ALL_SLOTS(SUCCESS_MARKER, FAILURE_MARKER) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS, saveLoadAllSlots, { \ + .successMarker = SUCCESS_MARKER, \ + .failureMarker = FAILURE_MARKER \ + }) + /** * Starts a save-load-all-slots item: calls saveLoadAllSlots() and jumps * straight to the item's successMarker or failureMarker via diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h index de0cd558..5467bc68 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h @@ -15,6 +15,21 @@ typedef struct { uiemojitype_t emojiType; } cutsceneemoji_t; +/** + * Shows an emoji above an entity for a duration, then completes + * immediately. + * + * @param ENTITY_INDEX Entity index to show the emoji above. + * @param EMOJI_TYPE uiemojitype_t to show. + * @param DURATION Duration in seconds to show it. + */ +#define CUTSCENE_EMOJI(ENTITY_INDEX, EMOJI_TYPE, DURATION) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_EMOJI, emoji, { \ + .entityIndex = ENTITY_INDEX, \ + .emojiType = EMOJI_TYPE, \ + .duration = DURATION \ + }) + /** * Starts an emoji step (shows an emoji above the entity for the given * duration, then completes immediately). diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h b/src/dusk/rpg/cutscene/item/ui/cutscenefade.h index c80661c3..952acd46 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenefade.h @@ -17,6 +17,52 @@ typedef struct { easingtype_t easing; } cutscenefade_t; +/** + * Longhand - begins a full-screen overlay transition between two colors. + * See the CUTSCENE_FADE_(TO|FROM)_(BLACK|WHITE) shorthands below. + * + * @param FROM Starting color_t. + * @param TO Ending color_t. + * @param DURATION Duration in seconds. + * @param EASING easingtype_t to apply. + */ +#define CUTSCENE_FADE(FROM, TO, DURATION, EASING) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_FADE, fade, { \ + .from = FROM, .to = TO, .duration = DURATION, .easing = EASING \ + }) + +/** + * Shorthand: fades from transparent to black over DURATION seconds. + * + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_FADE_TO_BLACK(DURATION) \ + CUTSCENE_FADE(COLOR_TRANSPARENT_BLACK, COLOR_BLACK, DURATION, EASING_LINEAR) + +/** + * Shorthand: fades from black to transparent over DURATION seconds. + * + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_FADE_FROM_BLACK(DURATION) \ + CUTSCENE_FADE(COLOR_BLACK, COLOR_TRANSPARENT_BLACK, DURATION, EASING_LINEAR) + +/** + * Shorthand: fades from transparent to white over DURATION seconds. + * + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_FADE_TO_WHITE(DURATION) \ + CUTSCENE_FADE(COLOR_TRANSPARENT_WHITE, COLOR_WHITE, DURATION, EASING_LINEAR) + +/** + * Shorthand: fades from white to transparent over DURATION seconds. + * + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_FADE_FROM_WHITE(DURATION) \ + CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR) + /** * Starts a fade item (begins the overlay transition). * diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h index ae97ebb2..7195e39e 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h @@ -14,6 +14,19 @@ typedef struct { uikeyboardopen_t open; } cutscenekeyboard_t; +/** + * Opens the on-screen keyboard and blocks the cutscene until it closes, + * then caches whatever was typed - see cutsceneSystemGetTextCache to + * read it back afterwards. + * + * @param ... uikeyboardopen_t designated initializers, e.g. + * CUTSCENE_KEYBOARD(.cancel = true, .maxLength = 8). + */ +#define CUTSCENE_KEYBOARD(...) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_KEYBOARD, keyboard, { \ + .open = { __VA_ARGS__ } \ + }) + /** * Starts a keyboard item (opens the on-screen keyboard with the item's * open parameters, unmodified - see uiKeyboardOpen). diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h index 761be491..2e6b7547 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h @@ -41,6 +41,59 @@ typedef struct { cutscenemodaloptioncallback_t callback; } cutscenemodal_t; +/** + * Shows a message-only modal (no option buttons) and immediately + * continues on to whatever follows this item - it does not wait for + * the dialog to be dismissed. Script the rest of the interaction (e.g. + * CUTSCENE_CALLBACK to kick off work, CUTSCENE_WAIT, then + * CUTSCENE_MODAL_CLOSE) as later items in the same cutscene. + * + * @param TITLE Modal title - displayed as-is unless it matches a locale + * message ID, in which case the translated string is shown - see + * uiModalLocalize. + * @param MESSAGE Modal message, same locale-matching rule as TITLE. + */ +#define CUTSCENE_MODAL(TITLE, MESSAGE) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MODAL, modal, { \ + .title = TITLE, .message = MESSAGE \ + }) + +/** + * Shows a modal with option buttons and immediately continues on, same + * as CUTSCENE_MODAL - it does not block waiting for a selection. + * Back/cancel input is disabled while it's open (see + * uiMenuSetDisableBack), so it must be dismissed by picking one; CALLBACK + * then fires with the selected option index once the dialog closes. + * + * @param TITLE Modal title (locale-matched, see CUTSCENE_MODAL). + * @param MESSAGE Modal message (locale-matched, see CUTSCENE_MODAL). + * @param CALLBACK cutscenemodaloptioncallback_t fired with the selected + * option index once the dialog closes. + * @param ... Option label strings, e.g. + * CUTSCENE_MODAL_OPTIONS(title, message, callback, "Retry", "Cancel") - + * not copied by this item, so they must stay valid until the modal opens + * (string literals are fine). Each is translated if it matches a locale + * message ID, same as TITLE/MESSAGE. + */ +#define CUTSCENE_MODAL_OPTIONS(TITLE, MESSAGE, CALLBACK, ...) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MODAL, modal, { \ + .title = TITLE, .message = MESSAGE, \ + .options = (const char_t *[]){ __VA_ARGS__ }, \ + .optionCount = (uint8_t)( \ + sizeof((const char_t *[]){ __VA_ARGS__ }) / sizeof(const char_t *) \ + ), \ + .callback = CALLBACK \ + }) + +/** + * Closes the currently open modal (if any). Useful when a modal was + * opened outside of a blocking CUTSCENE_MODAL item (e.g. directly via + * uiModalOpen) and this cutscene just needs to dismiss it and continue + * on to whatever follows this item in the sequence. + */ +#define CUTSCENE_MODAL_CLOSE() \ + { .type = CUTSCENE_ITEM_TYPE_MODAL_CLOSE } + /** * Starts a modal item (shows the modal dialog with the item's title, * message, and options, wiring callback to fire with the selected diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h index 642dbf92..895e1af8 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h @@ -7,6 +7,7 @@ #pragma once #include "rpg/cutscene/item/cutsceneitembase.h" +#include "rpg/cutscene/item/ui/cutscenemodal.h" // Backs CUTSCENE_MODAL_OPTIONS_ONE and CUTSCENE_MODAL_OPTIONS_TWO, so two // is the most either macro ever needs. @@ -25,6 +26,49 @@ typedef struct { uint8_t optionCount; } cutscenemodaloptionsmarkers_t; +/** + * Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly one option and + * MARKER1 to jump straight to via cutsceneGoTo once it's selected - no + * callback function to write. Does not fall through to whatever follows + * this item, so MARKER1 must be scripted elsewhere in the same cutscene. + * + * @param TITLE Modal title (locale-matched, see CUTSCENE_MODAL). + * @param MESSAGE Modal message (locale-matched, see CUTSCENE_MODAL). + * @param OPTION1 The single option's label. + * @param MARKER1 Marker to jump to when OPTION1 is selected. + */ +#define CUTSCENE_MODAL_OPTIONS_ONE(TITLE, MESSAGE, OPTION1, MARKER1) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, modalOptionsMarkers, { \ + .title = TITLE, .message = MESSAGE, \ + .options = { OPTION1 }, \ + .markers = { MARKER1 }, \ + .optionCount = 1 \ + }) + +/** + * Same as CUTSCENE_MODAL_OPTIONS, but fixed to exactly two options, + * jumping straight to OPTION1_MARKER or OPTION2_MARKER via cutsceneGoTo + * once the corresponding option is selected - no callback function to + * write. Does not fall through to whatever follows this item, so both + * markers must be scripted elsewhere in the same cutscene. + * + * @param TITLE Modal title (locale-matched, see CUTSCENE_MODAL). + * @param MESSAGE Modal message (locale-matched, see CUTSCENE_MODAL). + * @param OPTION1 First option's label. + * @param OPTION1_MARKER Marker to jump to when OPTION1 is selected. + * @param OPTION2 Second option's label. + * @param OPTION2_MARKER Marker to jump to when OPTION2 is selected. + */ +#define CUTSCENE_MODAL_OPTIONS_TWO( \ + TITLE, MESSAGE, OPTION1, OPTION1_MARKER, OPTION2, OPTION2_MARKER \ +) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, modalOptionsMarkers, { \ + .title = TITLE, .message = MESSAGE, \ + .options = { OPTION1, OPTION2 }, \ + .markers = { OPTION1_MARKER, OPTION2_MARKER }, \ + .optionCount = 2 \ + }) + /** * Starts a modal-options-markers item (shows the modal dialog with the * item's title, message, and options). diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h index 8e72a104..cc94d2ba 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h @@ -13,6 +13,18 @@ typedef struct { float_t duration; } cutsceneshake_t; +/** + * Kicks off a camera shake immediately. + * + * @param AMOUNT Shake amount, 0 (no shake) to 4 (three tiles): 1 is half + * a tile, 2 is a full tile, 3 is two tiles, and 4 is three tiles. + * @param DURATION Duration in seconds. + */ +#define CUTSCENE_SHAKE(AMOUNT, DURATION) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_SHAKE, shake, { \ + .amount = AMOUNT, .duration = DURATION \ + }) + /** * Starts a camera shake item (kicks off the shake on the RPG camera). * diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h b/src/dusk/rpg/cutscene/item/ui/cutscenetext.h index a5a2abb0..eb4aa62f 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetext.h @@ -14,6 +14,14 @@ typedef struct { char_t text[CUTSCENE_TEXT_MAX_CHARS]; } cutscenetext_t; +/** + * Shows the textbox with TEXT, blocking until it's dismissed. + * + * @param TEXT Text to display. + */ +#define CUTSCENE_TEXT(TEXT) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_TEXT, text, { .text = TEXT }) + /** * Starts a text item (shows the textbox with the item's text). * diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h index 905d033b..50750f82 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h @@ -16,6 +16,23 @@ typedef struct { float_t duration; } cutscenetextmini_t; +/** + * Shows a mini textbox at a world position for a duration, then + * completes immediately. + * + * @param TEXT Text to display. + * @param X World X of the mini textbox. + * @param Y World Y of the mini textbox. + * @param Z World Z of the mini textbox. + * @param DURATION Duration in seconds to show it. + */ +#define CUTSCENE_TEXT_MINI(TEXT, X, Y, Z, DURATION) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_TEXT_MINI, textMini, { \ + .text = TEXT, \ + .position = { X, Y, Z }, \ + .duration = DURATION \ + }) + /** * Starts a mini text item (shows a mini textbox at the given world * position for the given duration, then completes immediately). diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h index 6f063e8a..db8826f8 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h +++ b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h @@ -12,6 +12,16 @@ typedef struct { uint8_t index; } cutscenetextminihide_t; +/** + * Closes a mini textbox immediately. + * + * @param INDEX Mini textbox index to close. + */ +#define CUTSCENE_TEXT_MINI_HIDE(INDEX) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, textMiniHide, { \ + .index = INDEX \ + }) + /** * Starts a mini text hide step (closes the mini textbox immediately). * diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h index 58eba6bc..54a8c513 100644 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h @@ -21,6 +21,15 @@ typedef struct { uiscreentype_t screen; } cutsceneuishow_t; +/** + * Opens a full-screen UI panel (e.g. the main menu) declaratively, then + * immediately continues on to whatever follows this item. + * + * @param SCREEN uiscreentype_t to open. + */ +#define CUTSCENE_UI_SHOW(SCREEN) \ + CUTSCENE_ITEM(CUTSCENE_ITEM_TYPE_UI_SHOW, uiShow, { .screen = SCREEN }) + /** * Starts a UI_SHOW item, opening the requested full-screen UI panel. *