diff --git a/assets/cutscenes/battle/moves/fire.jsonc b/assets/cutscenes/battle/moves/fire.jsonc index b4760f18..8c88770a 100644 --- a/assets/cutscenes/battle/moves/fire.jsonc +++ b/assets/cutscenes/battle/moves/fire.jsonc @@ -11,6 +11,11 @@ }, { "type": "MODAL_CLOSE" + }, + { + "type": "BATTLE_FIGHTER_DAMAGE", + "fighter": "target", + "amount": 12 } ] } diff --git a/assets/cutscenes/battle/moves/slash.jsonc b/assets/cutscenes/battle/moves/slash.jsonc index 9726503b..0e80637f 100644 --- a/assets/cutscenes/battle/moves/slash.jsonc +++ b/assets/cutscenes/battle/moves/slash.jsonc @@ -11,6 +11,11 @@ }, { "type": "MODAL_CLOSE" + }, + { + "type": "BATTLE_FIGHTER_DAMAGE", + "fighter": "target", + "amount": 8 } ] } diff --git a/src/dusk/rpg/battle/battle.h b/src/dusk/rpg/battle/battle.h index 9c20aacb..108a0742 100644 --- a/src/dusk/rpg/battle/battle.h +++ b/src/dusk/rpg/battle/battle.h @@ -12,13 +12,13 @@ #include "rpg/battle/fighter/battlefighterpos.h" #include "rpg/battle/state/battlestate.h" -// typedef enum { -// BATTLE_RESULT_NONE, -// BATTLE_RESULT_WIN, -// BATTLE_RESULT_LOSS, -// BATTLE_RESULT_FLED, -// BATTLE_RESULT_COUNT -// } battleresult_t; +typedef enum { + BATTLE_RESULT_NONE, + BATTLE_RESULT_WIN, + BATTLE_RESULT_LOSS, + BATTLE_RESULT_FLED, + BATTLE_RESULT_COUNT +} battleresult_t; typedef struct { battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]; @@ -33,6 +33,11 @@ typedef struct { battlestate_t state; battlestatedata_t stateData; + + // Set once a side has been wiped out (see cutsceneBattlePostMoveStart) + // or the player has fled (see battleStateSelectionPlayerFlee). Stays + // BATTLE_RESULT_NONE for the rest of the battle otherwise. + battleresult_t result; } battle_t; extern battle_t BATTLE; diff --git a/src/dusk/rpg/battle/fighter/battlefighterpos.h b/src/dusk/rpg/battle/fighter/battlefighterpos.h index 98389c40..e7f237b0 100644 --- a/src/dusk/rpg/battle/fighter/battlefighterpos.h +++ b/src/dusk/rpg/battle/fighter/battlefighterpos.h @@ -24,4 +24,12 @@ typedef uint8_t battlefighterpos_t; #define BATTLE_FIGHTER_POS_6 BATTLE_FIGHTER_POS(6) #define BATTLE_FIGHTER_POS_7 BATTLE_FIGHTER_POS(7) -#define BATTLE_FIGHTER_POS_COUNT (BATTLE_FIGHTER_POS_7 + 1) \ No newline at end of file +#define BATTLE_FIGHTER_POS_COUNT (BATTLE_FIGHTER_POS_7 + 1) + +// Sentinels resolved by battleStateExecutingInit against the currently +// resolving action, for cutscene items authored inside a move's own +// cutscene (e.g. cutscenes/battle/moves/slash.jsonc) that need to refer +// to "whoever is being hit"/"whoever is attacking" without knowing their +// concrete BATTLE.fighters index ahead of time. +#define BATTLE_FIGHTER_POS_TARGET BATTLE_FIGHTER_POS(0xFE) +#define BATTLE_FIGHTER_POS_USER BATTLE_FIGHTER_POS(0xFD) \ No newline at end of file diff --git a/src/dusk/rpg/battle/state/battlestateexecuting.c b/src/dusk/rpg/battle/state/battlestateexecuting.c index 948a217c..ce8bf499 100644 --- a/src/dusk/rpg/battle/state/battlestateexecuting.c +++ b/src/dusk/rpg/battle/state/battlestateexecuting.c @@ -18,6 +18,18 @@ #include "util/string.h" +void battleStateExecutingResolveFighterIndex( + battlefighterpos_t *fighterIndex, + const uint8_t userIndex, + const battlemoveaction_t *action +) { + if(*fighterIndex == BATTLE_FIGHTER_POS_TARGET) { + *fighterIndex = action->targets[0]; + } else if(*fighterIndex == BATTLE_FIGHTER_POS_USER) { + *fighterIndex = userIndex; + } +} + void battleStateExecutingInit(void) { cutsceneitem_t items[BATTLE_STATE_EXECUTING_ITEMS_MAX]; uint8_t itemsCount = 0; @@ -60,6 +72,7 @@ void battleStateExecutingInit(void) { } // Parse out cutscene items and append to the list. + const uint8_t moveItemsStart = itemsCount; cutscene_t scene; errorret_t parseResult = cutsceneParseDoc( entry->data.json, &scene, &items[itemsCount], @@ -75,6 +88,21 @@ void battleStateExecutingInit(void) { continue; } + // Resolve BATTLE_FIGHTER_POS_TARGET/USER sentinels against this + // action, now that its user/target fighters are known. + for(uint8_t j = moveItemsStart; j < itemsCount; j++) { + cutsceneitem_t *moveItem = &items[j]; + if(moveItem->type == CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE) { + battleStateExecutingResolveFighterIndex( + &moveItem->battleFighterDamage.fighterIndex, i, action + ); + } else if(moveItem->type == CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL) { + battleStateExecutingResolveFighterIndex( + &moveItem->battleFighterHeal.fighterIndex, i, action + ); + } + } + // Insert end of attack cutscene item. items[itemsCount++] = (cutsceneitem_t){ .type = CUTSCENE_ITEM_TYPE_BATTLE_POST_MOVE diff --git a/src/dusk/rpg/battle/state/battlestateexecuting.h b/src/dusk/rpg/battle/state/battlestateexecuting.h index a6f8f75a..8e51d3e9 100644 --- a/src/dusk/rpg/battle/state/battlestateexecuting.h +++ b/src/dusk/rpg/battle/state/battlestateexecuting.h @@ -7,6 +7,8 @@ #pragma once #include "dusk.h" +#include "rpg/battle/fighter/battlefighterpos.h" +#include "rpg/battle/move/battlemoveaction.h" typedef struct { @@ -19,6 +21,24 @@ typedef struct { */ void battleStateExecutingInit(void); +/** + * Resolves a BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER sentinel + * (as parsed onto a BATTLE_FIGHTER_DAMAGE/BATTLE_FIGHTER_HEAL item inside + * a move's own cutscene) against the action currently being resolved + * into cutscene items. Leaves fighterIndex untouched if it's already a + * concrete BATTLE.fighters index. + * + * @param fighterIndex The field to resolve in place. + * @param userIndex Index into BATTLE.fighters of the fighter performing + * the action. + * @param action The action being resolved. + */ +void battleStateExecutingResolveFighterIndex( + battlefighterpos_t *fighterIndex, + const uint8_t userIndex, + const battlemoveaction_t *action +); + /** * Updates BATTLE_STATE_EXECUTING for one frame. */ diff --git a/src/dusk/rpg/battle/state/battlestatefled.c b/src/dusk/rpg/battle/state/battlestatefled.c index 1fbbed4d..441072a2 100644 --- a/src/dusk/rpg/battle/state/battlestatefled.c +++ b/src/dusk/rpg/battle/state/battlestatefled.c @@ -6,9 +6,21 @@ */ #include "battlestatefled.h" +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" void battleStateFledInit(void) { - + const cutsceneitem_t items[] = { + { + .type = CUTSCENE_ITEM_TYPE_TEXT, + .text = { .text = "Got away safely!" } + }, + { + .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, + .battleSetState = { .state = BATTLE_STATE_ENDING } + } + }; + cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); } void battleStateFledUpdate(void) { diff --git a/src/dusk/rpg/battle/state/battlestatefled.h b/src/dusk/rpg/battle/state/battlestatefled.h index 1cf2ef39..f482b6be 100644 --- a/src/dusk/rpg/battle/state/battlestatefled.h +++ b/src/dusk/rpg/battle/state/battlestatefled.h @@ -13,7 +13,9 @@ typedef struct { } battlestatefled_t; /** - * Called when the battle enters BATTLE_STATE_FLED. + * Called when the battle enters BATTLE_STATE_FLED. Queues a textbox + * announcing the escape, then transitions to BATTLE_STATE_ENDING once + * dismissed. */ void battleStateFledInit(void); diff --git a/src/dusk/rpg/battle/state/battlestatelost.c b/src/dusk/rpg/battle/state/battlestatelost.c index e160279b..334d8d87 100644 --- a/src/dusk/rpg/battle/state/battlestatelost.c +++ b/src/dusk/rpg/battle/state/battlestatelost.c @@ -6,9 +6,21 @@ */ #include "battlestatelost.h" +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" void battleStateLostInit(void) { - + const cutsceneitem_t items[] = { + { + .type = CUTSCENE_ITEM_TYPE_TEXT, + .text = { .text = "You were defeated..." } + }, + { + .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, + .battleSetState = { .state = BATTLE_STATE_ENDING } + } + }; + cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); } void battleStateLostUpdate(void) { diff --git a/src/dusk/rpg/battle/state/battlestatelost.h b/src/dusk/rpg/battle/state/battlestatelost.h index 202de391..7a20dca7 100644 --- a/src/dusk/rpg/battle/state/battlestatelost.h +++ b/src/dusk/rpg/battle/state/battlestatelost.h @@ -13,7 +13,9 @@ typedef struct { } battlestatelost_t; /** - * Called when the battle enters BATTLE_STATE_LOST. + * Called when the battle enters BATTLE_STATE_LOST. Queues a textbox + * announcing the loss, then transitions to BATTLE_STATE_ENDING once + * dismissed. */ void battleStateLostInit(void); diff --git a/src/dusk/rpg/battle/state/battlestateselection.c b/src/dusk/rpg/battle/state/battlestateselection.c index 168e7c2b..80ce4a31 100644 --- a/src/dusk/rpg/battle/state/battlestateselection.c +++ b/src/dusk/rpg/battle/state/battlestateselection.c @@ -101,5 +101,6 @@ void battleStateSelectionPlayerFlee(void) { battlefighter_t *fighter = battleStateSelectionGetCurrentFighter(); if(fighter == NULL) return; + BATTLE.result = BATTLE_RESULT_FLED; battleSetState(BATTLE_STATE_FLED); } \ No newline at end of file diff --git a/src/dusk/rpg/battle/state/battlestatewon.c b/src/dusk/rpg/battle/state/battlestatewon.c index bfd92054..95d09f06 100644 --- a/src/dusk/rpg/battle/state/battlestatewon.c +++ b/src/dusk/rpg/battle/state/battlestatewon.c @@ -6,9 +6,21 @@ */ #include "battlestatewon.h" +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" void battleStateWonInit(void) { - + const cutsceneitem_t items[] = { + { + .type = CUTSCENE_ITEM_TYPE_TEXT, + .text = { .text = "You won the battle!" } + }, + { + .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, + .battleSetState = { .state = BATTLE_STATE_ENDING } + } + }; + cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); } void battleStateWonUpdate(void) { diff --git a/src/dusk/rpg/battle/state/battlestatewon.h b/src/dusk/rpg/battle/state/battlestatewon.h index e7b920e6..d9c72dec 100644 --- a/src/dusk/rpg/battle/state/battlestatewon.h +++ b/src/dusk/rpg/battle/state/battlestatewon.h @@ -13,7 +13,9 @@ typedef struct { } battlestatewon_t; /** - * Called when the battle enters BATTLE_STATE_WON. + * Called when the battle enters BATTLE_STATE_WON. Queues a textbox + * announcing the win, then transitions to BATTLE_STATE_ENDING once + * dismissed. */ void battleStateWonInit(void); diff --git a/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt b/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt index 80b57f57..55c2fdd3 100644 --- a/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt +++ b/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt @@ -9,4 +9,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} cutscenebattlesetstate.c cutscenebattlewaitselection.c cutscenebattlepostmove.c + cutscenebattlefighterdamage.c + cutscenebattlefighterheal.c ) diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c new file mode 100644 index 00000000..36a9bdbf --- /dev/null +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c @@ -0,0 +1,68 @@ +/** + * 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/battle/battle.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" +#include "assert/assert.h" +#include "util/string.h" + +#define CUTSCENE_BATTLE_FIGHTER_REF_MAX 16 + +void cutsceneBattleFighterDamageStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + const cutscenebattlefighterdamage_t *damage = &item->battleFighterDamage; + assertTrue( + damage->fighterIndex < BATTLE_FIGHTER_POS_COUNT, + "fighterIndex sentinel was never resolved" + ); + battlefighter_t *fighter = BATTLE.fighters[damage->fighterIndex]; + assertNotNull(fighter, "Fighter cannot be NULL"); + + battleFighterDamage(fighter, damage->amount); +} + +bool_t cutsceneBattleFighterDamageUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + return true; +} + +errorret_t cutsceneBattleFighterDamageLoad( + yyjson_val *itemObj, + cutsceneitem_t *item +) { + char_t fighterRef[CUTSCENE_BATTLE_FIGHTER_REF_MAX]; + if(!cutsceneJsonCopyString( + itemObj, "fighter", NULL, fighterRef, CUTSCENE_BATTLE_FIGHTER_REF_MAX + )) { + errorThrow("Cutscene item (BATTLE_FIGHTER_DAMAGE): missing 'fighter'"); + } + if(stringEquals(fighterRef, "target")) { + item->battleFighterDamage.fighterIndex = BATTLE_FIGHTER_POS_TARGET; + } else if(stringEquals(fighterRef, "user")) { + item->battleFighterDamage.fighterIndex = BATTLE_FIGHTER_POS_USER; + } else { + errorThrow( + "Cutscene item (BATTLE_FIGHTER_DAMAGE): 'fighter' must be " + "'target' or 'user'" + ); + } + + const int32_t amount = cutsceneJsonGetInt(itemObj, "amount", -1); + if(amount < 0 || amount > UINT16_MAX) { + errorThrow( + "Cutscene item (BATTLE_FIGHTER_DAMAGE): invalid/missing 'amount'" + ); + } + item->battleFighterDamage.amount = (uint16_t)amount; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h new file mode 100644 index 00000000..7d592732 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/cutscene/item/cutsceneitembase.h" +#include "rpg/battle/fighter/battlefighterpos.h" + +typedef struct { + // Index into BATTLE.fighters of the fighter to damage, or one of + // BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER, resolved against + // the currently-executing action by battleStateExecutingInit before + // this item ever runs. + battlefighterpos_t fighterIndex; + uint16_t amount; +} cutscenebattlefighterdamage_t; + +/** + * Starts a BATTLE_FIGHTER_DAMAGE item, applying the configured damage to + * BATTLE.fighters[fighterIndex] immediately. + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneBattleFighterDamageStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates a BATTLE_FIGHTER_DAMAGE item (always completes immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true always. + */ +bool_t cutsceneBattleFighterDamageUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Parses a BATTLE_FIGHTER_DAMAGE cutscene item's JSON fields into item. + * + * @param itemObj The item's JSON object. + * @param item Destination item, already zeroed by the caller. + * @return Error code indicating success or failure of the parse. + */ +errorret_t cutsceneBattleFighterDamageLoad( + yyjson_val *itemObj, + cutsceneitem_t *item +); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c new file mode 100644 index 00000000..ee288541 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c @@ -0,0 +1,68 @@ +/** + * 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/battle/battle.h" +#include "rpg/cutscene/item/json/cutscenejsonfield.h" +#include "assert/assert.h" +#include "util/string.h" + +#define CUTSCENE_BATTLE_FIGHTER_REF_MAX 16 + +void cutsceneBattleFighterHealStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + const cutscenebattlefighterheal_t *heal = &item->battleFighterHeal; + assertTrue( + heal->fighterIndex < BATTLE_FIGHTER_POS_COUNT, + "fighterIndex sentinel was never resolved" + ); + battlefighter_t *fighter = BATTLE.fighters[heal->fighterIndex]; + assertNotNull(fighter, "Fighter cannot be NULL"); + + battleFighterHeal(fighter, heal->amount); +} + +bool_t cutsceneBattleFighterHealUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + return true; +} + +errorret_t cutsceneBattleFighterHealLoad( + yyjson_val *itemObj, + cutsceneitem_t *item +) { + char_t fighterRef[CUTSCENE_BATTLE_FIGHTER_REF_MAX]; + if(!cutsceneJsonCopyString( + itemObj, "fighter", NULL, fighterRef, CUTSCENE_BATTLE_FIGHTER_REF_MAX + )) { + errorThrow("Cutscene item (BATTLE_FIGHTER_HEAL): missing 'fighter'"); + } + if(stringEquals(fighterRef, "target")) { + item->battleFighterHeal.fighterIndex = BATTLE_FIGHTER_POS_TARGET; + } else if(stringEquals(fighterRef, "user")) { + item->battleFighterHeal.fighterIndex = BATTLE_FIGHTER_POS_USER; + } else { + errorThrow( + "Cutscene item (BATTLE_FIGHTER_HEAL): 'fighter' must be " + "'target' or 'user'" + ); + } + + const int32_t amount = cutsceneJsonGetInt(itemObj, "amount", -1); + if(amount < 0 || amount > UINT16_MAX) { + errorThrow( + "Cutscene item (BATTLE_FIGHTER_HEAL): invalid/missing 'amount'" + ); + } + item->battleFighterHeal.amount = (uint16_t)amount; + + errorOk(); +} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h new file mode 100644 index 00000000..ae414b9d --- /dev/null +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "rpg/cutscene/item/cutsceneitembase.h" +#include "rpg/battle/fighter/battlefighterpos.h" + +typedef struct { + // Index into BATTLE.fighters of the fighter to heal, or one of + // BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER, resolved against + // the currently-executing action by battleStateExecutingInit before + // this item ever runs. + battlefighterpos_t fighterIndex; + uint16_t amount; +} cutscenebattlefighterheal_t; + +/** + * Starts a BATTLE_FIGHTER_HEAL item, applying the configured heal to + * BATTLE.fighters[fighterIndex] immediately. + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneBattleFighterHealStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates a BATTLE_FIGHTER_HEAL item (always completes immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true always. + */ +bool_t cutsceneBattleFighterHealUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Parses a BATTLE_FIGHTER_HEAL cutscene item's JSON fields into item. + * + * @param itemObj The item's JSON object. + * @param item Destination item, already zeroed by the caller. + * @return Error code indicating success or failure of the parse. + */ +errorret_t cutsceneBattleFighterHealLoad( + yyjson_val *itemObj, + cutsceneitem_t *item +); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c index 77efff1a..9c1136dc 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c @@ -6,15 +6,44 @@ */ #include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/battle/battle.h" +#include "rpg/cutscene/cutscenesystem.h" #include "console/console.h" void cutsceneBattlePostMoveStart( const cutsceneitem_t *item, cutsceneitemdata_t *data ) { - // TODO: check fighter health etc. here to short-circuit into a - // win/loss (BATTLE_STATE_WON/LOST) once a side has been wiped out. consolePrint("BATTLE_POST_MOVE\n"); + + // Once a result has already been decided (e.g. the player fled), there + // is nothing left to check. + if(BATTLE.result != BATTLE_RESULT_NONE) return; + + bool_t allyAlive = false; + bool_t enemyAlive = false; + for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { + battlefighter_t *fighter = BATTLE.fighters[i]; + if(fighter == NULL) continue; + if(!battleFighterIsAlive(fighter)) continue; + + if(fighter->team == BATTLE_FIGHTER_TEAM_ALLY) allyAlive = true; + else enemyAlive = true; + } + + if(allyAlive && enemyAlive) return; + + BATTLE.result = allyAlive ? BATTLE_RESULT_WIN : BATTLE_RESULT_LOSS; + + const cutsceneitem_t items[] = { + { + .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, + .battleSetState = { + .state = allyAlive ? BATTLE_STATE_WON : BATTLE_STATE_LOST + } + } + }; + cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); } bool_t cutsceneBattlePostMoveUpdate( diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h index 6a7ff0ca..58b62943 100644 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h +++ b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h @@ -10,10 +10,11 @@ /** * Starts a BATTLE_POST_MOVE item. Runs after each queued move's cutscene - * finishes - eventually this is where we'll check things like fighter - * health to short-circuit into a win/loss, but for now it just prints to - * the console. Not JSON-authorable -- constructed dynamically by - * battleStateExecutingInit. + * finishes: checks whether every ally or every enemy fighter has been + * knocked out and, if so, sets BATTLE.result and splices in a + * BATTLE_SET_STATE to BATTLE_STATE_WON/LOST. No-ops the health check if + * BATTLE.result is already set (e.g. the player fled). Not + * JSON-authorable -- constructed dynamically by battleStateExecutingInit. * * @param item The cutscene item. * @param data Runtime data storage. diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index ba44a1bb..3f10f891 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -270,6 +270,20 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { .update = cutsceneBattlePostMoveUpdate }, + [CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE] = { + .init = cutsceneBattleFighterDamageStart, + .update = cutsceneBattleFighterDamageUpdate, + .jsonName = "BATTLE_FIGHTER_DAMAGE", + .load = cutsceneBattleFighterDamageLoad + }, + + [CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL] = { + .init = cutsceneBattleFighterHealStart, + .update = cutsceneBattleFighterHealUpdate, + .jsonName = "BATTLE_FIGHTER_HEAL", + .load = cutsceneBattleFighterHealLoad + }, + // Save [CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = { .init = cutsceneSaveDeviceCheckStart, diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index 3530fa2e..5d53190a 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -44,6 +44,8 @@ #include "battle/cutscenebattlesetstate.h" #include "battle/cutscenebattlewaitselection.h" #include "battle/cutscenebattlepostmove.h" +#include "battle/cutscenebattlefighterdamage.h" +#include "battle/cutscenebattlefighterheal.h" #include "save/cutscenesavedevicecheck.h" #include "save/cutscenesaveloadallslots.h" #include "audio/cutsceneaudioplay.h" @@ -118,6 +120,8 @@ struct cutsceneitem_s { cutscenemapareawait_t mapAreaWait; cutscenebattlestart_t battleStart; cutscenebattlesetstate_t battleSetState; + cutscenebattlefighterdamage_t battleFighterDamage; + cutscenebattlefighterheal_t battleFighterHeal; cutsceneemoji_t emoji; cutsceneshake_t shake; cutscenemodal_t modal; diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.h b/src/dusk/rpg/cutscene/item/cutsceneitembase.h index b3f7dcc7..107523d8 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitembase.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitembase.h @@ -64,6 +64,8 @@ typedef enum { CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION, CUTSCENE_ITEM_TYPE_BATTLE_POST_MOVE, + CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE, + CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL, // Save CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,