Drive battle rounds through cutscene items, fix render/init bugs
Adds BATTLE_START, BATTLE_SET_STATE and BATTLE_WAIT_SELECTION cutscene items so a battle's state transitions (opening -> pre-round -> selection -> executing) are chained through the running cutscene, with cutsceneSystemInsertItemsNext/InsertItems splitting "splice" from "splice and advance immediately". Adds battlestateselection's get-current-fighter/player-attack/player-flee API backing the battle UI, and battleMoveGetIdByName for JSON-authored ability lookups. Fixes: off-by-one in battleMoveInit that let the first-defined move alias BATTLE_MOVE_ID_NULL; a segfault in scenebattle.c from rendering unfilled (NULL) fighter slots; stale battleGetCurrentFighter/ battlePlayerAttack/battlePlayerFlee references in the battle UI. Adds assertions across the battle/scene/UI code for fighter-index bounds and non-null invariants. Updates battletest.jsonc for the new BATTLE_START item schema. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
battle_t BATTLE;
|
||||
|
||||
@@ -22,7 +23,8 @@ void battleStart(
|
||||
assertNotNull(fighters, "Fighters cannot be NULL");
|
||||
|
||||
#if DUSK_ASSERTIONS
|
||||
// Must be at least two non null fighter present.
|
||||
// Must be at least two non null fighter present, may need to ensure they
|
||||
// are from seperate teams also.
|
||||
uint8_t nonNullCount = 0;
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||
if(fighters[i] != NULL) nonNullCount++;
|
||||
@@ -39,18 +41,38 @@ void battleStart(
|
||||
sizeof(battlefighter_t *) * BATTLE_FIGHTER_POS_COUNT
|
||||
);
|
||||
|
||||
// Call init on opening state.
|
||||
BATTLE.state = BATTLE_STATE_OPENING;
|
||||
// Enter the opening state.
|
||||
battleSetState(BATTLE_STATE_OPENING);
|
||||
|
||||
// Change scene
|
||||
sceneSet(SCENE_TYPE_BATTLE);
|
||||
}
|
||||
|
||||
void battleSetState(const battlestate_t next) {
|
||||
assertTrue(next < BATTLE_STATE_COUNT, "Invalid battle state");
|
||||
assertNotNull(BATTLE_STATE_FUNCTIONS[next].init, "Missing state init function");
|
||||
assertNotNull(BATTLE_STATE_FUNCTIONS[next].update, "Missing state update function");
|
||||
assertNotNull(BATTLE_STATE_FUNCTIONS[next].dispose, "Missing state dispose function");
|
||||
|
||||
if(BATTLE.state != BATTLE_STATE_NULL) {
|
||||
BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose();
|
||||
}
|
||||
BATTLE.state = next;
|
||||
BATTLE_STATE_FUNCTIONS[BATTLE.state].init();
|
||||
}
|
||||
|
||||
void battleUpdate(void) {
|
||||
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
||||
assertTrue(BATTLE.state < BATTLE_STATE_COUNT, "Invalid battle state");
|
||||
assertNotNull(BATTLE_STATE_FUNCTIONS[BATTLE.state].update, "Missing state update function");
|
||||
|
||||
BATTLE_STATE_FUNCTIONS[BATTLE.state].update();
|
||||
}
|
||||
|
||||
void battleDispose(void) {
|
||||
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
||||
assertTrue(BATTLE.state < BATTLE_STATE_COUNT, "Invalid battle state");
|
||||
assertNotNull(BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose, "Missing state dispose function");
|
||||
|
||||
BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose();
|
||||
memoryZero(&BATTLE, sizeof(battle_t));
|
||||
|
||||
@@ -49,6 +49,14 @@ void battleStart(
|
||||
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]
|
||||
);
|
||||
|
||||
/**
|
||||
* Transitions the battle to a new state: disposes the current state (if
|
||||
* any) and initializes next.
|
||||
*
|
||||
* @param next The state to transition to.
|
||||
*/
|
||||
void battleSetState(const battlestate_t next);
|
||||
|
||||
/**
|
||||
* Updates the battle system.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "battlemove.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include <string.h>
|
||||
@@ -17,7 +18,7 @@ uint8_t BATTLE_MOVE_COUNT;
|
||||
|
||||
errorret_t battleMoveInit(void) {
|
||||
memoryZero(BATTLE_MOVES, sizeof(BATTLE_MOVES));
|
||||
BATTLE_MOVE_COUNT = BATTLE_MOVE_ID_NULL;
|
||||
BATTLE_MOVE_COUNT = BATTLE_MOVE_ID_NULL + 1;// Reserve NULL.
|
||||
|
||||
// Get moves JSON.
|
||||
assetentry_t *entry = assetLock(
|
||||
@@ -126,3 +127,13 @@ errorret_t battleMoveInit(void) {
|
||||
assetUnlockEntry(entry);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
battlemoveid_t battleMoveGetIdByName(const char_t *name) {
|
||||
assertNotNull(name, "name must not be NULL");
|
||||
|
||||
for(battlemoveid_t id = BATTLE_MOVE_ID_NULL + 1; id < BATTLE_MOVE_COUNT; id++) {
|
||||
if(stringEquals(BATTLE_MOVES[id].name, name)) return id;
|
||||
}
|
||||
|
||||
return BATTLE_MOVE_ID_NULL;
|
||||
}
|
||||
|
||||
@@ -32,3 +32,11 @@ extern battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t battleMoveInit(void);
|
||||
|
||||
/**
|
||||
* Finds a move's id by name (exact match).
|
||||
*
|
||||
* @param name The move name to search for.
|
||||
* @return The matching move's id, or BATTLE_MOVE_ID_NULL if not found.
|
||||
*/
|
||||
battlemoveid_t battleMoveGetIdByName(const char_t *name);
|
||||
|
||||
@@ -6,9 +6,17 @@
|
||||
*/
|
||||
|
||||
#include "battlestateopening.h"
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void battleStateOpeningInit(void) {
|
||||
|
||||
const cutsceneitem_t items[] = {
|
||||
{
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE,
|
||||
.battleSetState = { .state = BATTLE_STATE_PRE_ROUND }
|
||||
}
|
||||
};
|
||||
cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t));
|
||||
}
|
||||
|
||||
void battleStateOpeningUpdate(void) {
|
||||
|
||||
@@ -6,9 +6,20 @@
|
||||
*/
|
||||
|
||||
#include "battlestatepreround.h"
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
void battleStatePreRoundInit(void) {
|
||||
|
||||
const cutsceneitem_t items[] = {
|
||||
{
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE,
|
||||
.battleSetState = { .state = BATTLE_STATE_SELECTION }
|
||||
},
|
||||
{
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION
|
||||
}
|
||||
};
|
||||
cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t));
|
||||
}
|
||||
|
||||
void battleStatePreRoundUpdate(void) {
|
||||
|
||||
@@ -6,15 +6,114 @@
|
||||
*/
|
||||
|
||||
#include "battlestateselection.h"
|
||||
#include "rpg/battle/battle.h"
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
|
||||
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
|
||||
|
||||
void battleStateSelectionInit(void) {
|
||||
|
||||
BATTLE.stateData.selection.selectionIndex = 0;
|
||||
}
|
||||
|
||||
void battleStateSelectionUpdate(void) {
|
||||
battlestateselection_t *state = &BATTLE.stateData.selection;
|
||||
|
||||
while(state->selectionIndex < BATTLE_FIGHTER_POS_COUNT) {
|
||||
const uint8_t fighterIndex = state->selectionIndex;
|
||||
if(!battleStateSelectionFighterNeedsDecision(fighterIndex)) {
|
||||
state->selectionIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||
if(fighter->controller != BATTLE_FIGHTER_CONTROLLER_AI) {
|
||||
// Player-controlled: wait for battleStateSelectionPlayerAttack/Flee.
|
||||
return;
|
||||
}
|
||||
|
||||
const battlefighterteam_t enemyTeam =
|
||||
fighter->team == BATTLE_FIGHTER_TEAM_ALLY ?
|
||||
BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY;
|
||||
|
||||
uint8_t targetIndex = 0xFF;
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||
battlefighter_t *candidate = BATTLE.fighters[i];
|
||||
if(candidate == NULL) continue;
|
||||
if(candidate->team != enemyTeam) continue;
|
||||
if(!battleFighterIsAlive(candidate)) continue;
|
||||
targetIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if(targetIndex != 0xFF) {
|
||||
battleStateSelectionQueueAttack(fighterIndex, targetIndex);
|
||||
}
|
||||
state->selectionIndex++;
|
||||
}
|
||||
|
||||
const cutsceneitem_t items[] = {
|
||||
{
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE,
|
||||
.battleSetState = { .state = BATTLE_STATE_EXECUTING }
|
||||
}
|
||||
};
|
||||
cutsceneSystemInsertItems(items, sizeof(items) / sizeof(cutsceneitem_t));
|
||||
}
|
||||
|
||||
void battleStateSelectionDispose(void) {
|
||||
|
||||
}
|
||||
|
||||
bool_t battleStateSelectionFighterNeedsDecision(const uint8_t fighterIndex) {
|
||||
assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds");
|
||||
|
||||
battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||
if(fighter == NULL) return false;
|
||||
if(!battleFighterIsAlive(fighter)) return false;
|
||||
|
||||
return BATTLE.actionsPending[fighterIndex].move == BATTLE_MOVE_ID_NULL;
|
||||
}
|
||||
|
||||
void battleStateSelectionQueueAttack(
|
||||
const uint8_t fighterIndex,
|
||||
const uint8_t targetIndex
|
||||
) {
|
||||
assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds");
|
||||
assertTrue(targetIndex < BATTLE_FIGHTER_POS_COUNT, "targetIndex out of bounds");
|
||||
assertNotNull(BATTLE.fighters[fighterIndex], "Fighter cannot be NULL");
|
||||
assertNotNull(BATTLE.fighters[targetIndex], "Target cannot be NULL");
|
||||
|
||||
battlemoveaction_t *action = &BATTLE.actionsPending[fighterIndex];
|
||||
action->move = BATTLE_MOVE_ID_ATTACK;
|
||||
action->targets[0] = targetIndex;
|
||||
action->user = BATTLE.fighters[fighterIndex];
|
||||
}
|
||||
|
||||
battlefighter_t *battleStateSelectionGetCurrentFighter(void) {
|
||||
if(BATTLE.state != BATTLE_STATE_SELECTION) return NULL;
|
||||
|
||||
const uint8_t index = BATTLE.stateData.selection.selectionIndex;
|
||||
if(index >= BATTLE_FIGHTER_POS_COUNT) return NULL;
|
||||
|
||||
return BATTLE.fighters[index];
|
||||
}
|
||||
|
||||
void battleStateSelectionPlayerAttack(const uint8_t targetIndex) {
|
||||
battlefighter_t *fighter = battleStateSelectionGetCurrentFighter();
|
||||
if(fighter == NULL) return;
|
||||
|
||||
battleStateSelectionQueueAttack(
|
||||
BATTLE.stateData.selection.selectionIndex, targetIndex
|
||||
);
|
||||
BATTLE.stateData.selection.selectionIndex++;
|
||||
}
|
||||
|
||||
void battleStateSelectionPlayerFlee(void) {
|
||||
battlefighter_t *fighter = battleStateSelectionGetCurrentFighter();
|
||||
if(fighter == NULL) return;
|
||||
|
||||
battleSetState(BATTLE_STATE_FLED);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "rpg/battle/fighter/battlefighter.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
// Cursor into BATTLE.fighters (by position index) for the fighter
|
||||
// currently deciding a move this round.
|
||||
uint8_t selectionIndex;
|
||||
} battlestateselection_t;
|
||||
|
||||
/**
|
||||
@@ -18,7 +20,11 @@ typedef struct {
|
||||
void battleStateSelectionInit(void);
|
||||
|
||||
/**
|
||||
* Updates BATTLE_STATE_SELECTION for one frame.
|
||||
* Updates BATTLE_STATE_SELECTION for one frame: auto-decides for every
|
||||
* AI-controlled fighter the cursor passes, stopping once it reaches a
|
||||
* player-controlled fighter awaiting input (see
|
||||
* battleStateSelectionGetCurrentFighter), or transitions to
|
||||
* BATTLE_STATE_EXECUTING once every position has decided.
|
||||
*/
|
||||
void battleStateSelectionUpdate(void);
|
||||
|
||||
@@ -26,3 +32,49 @@ void battleStateSelectionUpdate(void);
|
||||
* Called when the battle leaves BATTLE_STATE_SELECTION.
|
||||
*/
|
||||
void battleStateSelectionDispose(void);
|
||||
|
||||
/**
|
||||
* Checks whether the fighter at fighterIndex is a live, undecided
|
||||
* candidate that BATTLE_STATE_SELECTION should still be deciding a move
|
||||
* for.
|
||||
*
|
||||
* @param fighterIndex Index into BATTLE.fighters to check.
|
||||
* @return True if the fighter is alive and has no move queued yet.
|
||||
*/
|
||||
bool_t battleStateSelectionFighterNeedsDecision(const uint8_t fighterIndex);
|
||||
|
||||
/**
|
||||
* Queues a basic attack from fighterIndex against targetIndex.
|
||||
*
|
||||
* @param fighterIndex Index into BATTLE.fighters of the deciding fighter.
|
||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
||||
*/
|
||||
void battleStateSelectionQueueAttack(
|
||||
const uint8_t fighterIndex,
|
||||
const uint8_t targetIndex
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the fighter currently awaiting a decision in
|
||||
* BATTLE_STATE_SELECTION.
|
||||
*
|
||||
* @return Pointer to the fighter awaiting a decision, or NULL if the
|
||||
* battle isn't in BATTLE_STATE_SELECTION or every position has
|
||||
* already decided.
|
||||
*/
|
||||
battlefighter_t *battleStateSelectionGetCurrentFighter(void);
|
||||
|
||||
/**
|
||||
* Submits the currently-selecting fighter's attack against a target, if
|
||||
* the battle is in BATTLE_STATE_SELECTION and awaiting a decision. Queues
|
||||
* the move and advances the selection cursor.
|
||||
*
|
||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
||||
*/
|
||||
void battleStateSelectionPlayerAttack(const uint8_t targetIndex);
|
||||
|
||||
/**
|
||||
* Ends the battle immediately with BATTLE_STATE_FLED, if the battle is in
|
||||
* BATTLE_STATE_SELECTION and awaiting a decision.
|
||||
*/
|
||||
void battleStateSelectionPlayerFlee(void);
|
||||
|
||||
@@ -87,20 +87,34 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
|
||||
cutsceneGoTo(marker);
|
||||
}
|
||||
|
||||
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
|
||||
assertNotNull(cutscene, "Cutscene cannot be NULL");
|
||||
void cutsceneSystemInsertItemsNext(
|
||||
const cutsceneitem_t *items,
|
||||
const size_t itemCount
|
||||
) {
|
||||
assertNotNull(items, "Items cannot be NULL");
|
||||
assertNotNull(
|
||||
CUTSCENE_SYSTEM.scene,
|
||||
"cutsceneSystemInsertCutscene called with no cutscene running"
|
||||
"cutsceneSystemInsertItemsNext called with no cutscene running"
|
||||
);
|
||||
|
||||
cutsceneAppendNext(
|
||||
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem,
|
||||
cutscene->items, cutscene->itemCount
|
||||
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem, items, itemCount
|
||||
);
|
||||
}
|
||||
|
||||
void cutsceneSystemInsertItems(
|
||||
const cutsceneitem_t *items,
|
||||
const size_t itemCount
|
||||
) {
|
||||
cutsceneSystemInsertItemsNext(items, itemCount);
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
|
||||
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
|
||||
assertNotNull(cutscene, "Cutscene cannot be NULL");
|
||||
cutsceneSystemInsertItems(cutscene->items, cutscene->itemCount);
|
||||
}
|
||||
|
||||
void cutsceneSystemLoad(const char_t *file) {
|
||||
assertNotNull(file, "File cannot be NULL");
|
||||
|
||||
|
||||
@@ -109,6 +109,32 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
|
||||
const char_t *marker
|
||||
);
|
||||
|
||||
/**
|
||||
* Splices items into the running cutscene so that items[0] becomes the
|
||||
* very next item, without otherwise disturbing the currently-running
|
||||
* item's progress.
|
||||
*
|
||||
* @param items Items to splice in.
|
||||
* @param itemCount Number of items in items.
|
||||
*/
|
||||
void cutsceneSystemInsertItemsNext(
|
||||
const cutsceneitem_t *items,
|
||||
const size_t itemCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Splices items into the running cutscene right after the current item
|
||||
* (see cutsceneSystemInsertItemsNext), then immediately advances into the
|
||||
* first spliced item.
|
||||
*
|
||||
* @param items Items to splice in.
|
||||
* @param itemCount Number of items in items.
|
||||
*/
|
||||
void cutsceneSystemInsertItems(
|
||||
const cutsceneitem_t *items,
|
||||
const size_t itemCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Splices cutscene's items into the running cutscene.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@ add_subdirectory(entity)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(maparea)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(battle)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(audio)
|
||||
add_subdirectory(json)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenebattlestart.c
|
||||
cutscenebattlesetstate.c
|
||||
cutscenebattlewaitselection.c
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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/cutscenejsonbattlestate.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void cutsceneBattleSetStateStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
assertNotNull(item, "item cannot be NULL");
|
||||
battleSetState(item->battleSetState.state);
|
||||
}
|
||||
|
||||
bool_t cutsceneBattleSetStateUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
errorret_t cutsceneBattleSetStateLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
battlestate_t state;
|
||||
if(!cutsceneJsonLookupBattleState(
|
||||
yyjson_get_str(yyjson_obj_get(itemObj, "state")), &state
|
||||
)) {
|
||||
errorThrow("Cutscene item (BATTLE_SET_STATE): invalid 'state'");
|
||||
}
|
||||
item->battleSetState.state = state;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/battle/state/battlestate.h"
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
|
||||
typedef struct {
|
||||
battlestate_t state;
|
||||
} cutscenebattlesetstate_t;
|
||||
|
||||
/**
|
||||
* Starts a BATTLE_SET_STATE item, transitioning the battle to the
|
||||
* configured state immediately.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneBattleSetStateStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a BATTLE_SET_STATE item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneBattleSetStateUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Parses a BATTLE_SET_STATE 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 cutsceneBattleSetStateLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
);
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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/battle/party.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
battlefighter_t CUTSCENE_BATTLE_START_ENEMY_POOL[
|
||||
CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX
|
||||
];
|
||||
|
||||
void cutsceneBattleStartStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
assertNotNull(item, "item cannot be NULL");
|
||||
|
||||
const cutscenebattlestart_t *config = &item->battleStart;
|
||||
assertTrue(
|
||||
config->enemyCount <= CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX,
|
||||
"enemyCount out of bounds"
|
||||
);
|
||||
|
||||
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT] = { 0 };
|
||||
|
||||
memoryZero(
|
||||
CUTSCENE_BATTLE_START_ENEMY_POOL,
|
||||
sizeof(CUTSCENE_BATTLE_START_ENEMY_POOL)
|
||||
);
|
||||
for(uint8_t i = 0; i < config->enemyCount; i++) {
|
||||
const cutscenebattlestartenemy_t *enemyConfig = &config->enemies[i];
|
||||
battlefighter_t *enemy = &CUTSCENE_BATTLE_START_ENEMY_POOL[i];
|
||||
|
||||
battleFighterInit(
|
||||
enemy, BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
||||
enemyConfig->stats
|
||||
);
|
||||
for(uint8_t a = 0; a < enemyConfig->abilityCount; a++) {
|
||||
battleFighterAbilityAdd(enemy, enemyConfig->abilities[a]);
|
||||
}
|
||||
|
||||
fighters[i] = enemy;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||
battlefighter_t *member = partyGetOrderMember(i);
|
||||
if(member == NULL) continue;
|
||||
|
||||
fighters[BATTLE_FIGHTER_POS_4 + i] = member;
|
||||
}
|
||||
|
||||
battleStart(fighters);
|
||||
}
|
||||
|
||||
bool_t cutsceneBattleStartUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
errorret_t cutsceneBattleStartLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies");
|
||||
size_t enemyCount = yyjson_arr_size(enemies);
|
||||
if(
|
||||
!yyjson_is_arr(enemies) || enemyCount == 0 ||
|
||||
enemyCount > CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX
|
||||
) {
|
||||
errorThrow(
|
||||
"Cutscene item (BATTLE_START): 'enemies' must have 1-%u entries",
|
||||
(uint32_t)CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX
|
||||
);
|
||||
}
|
||||
item->battleStart.enemyCount = (uint8_t)enemyCount;
|
||||
|
||||
yyjson_arr_iter iter = yyjson_arr_iter_with(enemies);
|
||||
yyjson_val *enemyVal;
|
||||
size_t e = 0;
|
||||
while((enemyVal = yyjson_arr_iter_next(&iter)) != NULL) {
|
||||
cutscenebattlestartenemy_t *enemy = &item->battleStart.enemies[e];
|
||||
|
||||
yyjson_val *stats = yyjson_obj_get(enemyVal, "stats");
|
||||
if(!yyjson_is_obj(stats)) {
|
||||
errorThrow(
|
||||
"Cutscene item (BATTLE_START): enemies[%u] is missing 'stats'",
|
||||
(uint32_t)e
|
||||
);
|
||||
}
|
||||
enemy->stats.attack = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "attack"));
|
||||
enemy->stats.defense = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "defense"));
|
||||
enemy->stats.magic = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "magic"));
|
||||
enemy->stats.speed = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "speed"));
|
||||
enemy->stats.luck = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "luck"));
|
||||
enemy->stats.healthMax = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "healthMax"));
|
||||
enemy->stats.mpMax = (uint16_t)yyjson_get_int(yyjson_obj_get(stats, "mpMax"));
|
||||
|
||||
yyjson_val *abilities = yyjson_obj_get(enemyVal, "abilities");
|
||||
if(abilities != NULL) {
|
||||
size_t abilityCount = yyjson_arr_size(abilities);
|
||||
if(
|
||||
!yyjson_is_arr(abilities) ||
|
||||
abilityCount > BATTLE_FIGHTER_ABILITY_COUNT_MAX
|
||||
) {
|
||||
errorThrow(
|
||||
"Cutscene item (BATTLE_START): enemies[%u].abilities must have "
|
||||
"0-%u entries",
|
||||
(uint32_t)e, (uint32_t)BATTLE_FIGHTER_ABILITY_COUNT_MAX
|
||||
);
|
||||
}
|
||||
|
||||
yyjson_arr_iter abilityIter = yyjson_arr_iter_with(abilities);
|
||||
yyjson_val *abilityVal;
|
||||
size_t a = 0;
|
||||
while((abilityVal = yyjson_arr_iter_next(&abilityIter)) != NULL) {
|
||||
const char_t *name = yyjson_get_str(abilityVal);
|
||||
battlemoveid_t ability = name == NULL
|
||||
? BATTLE_MOVE_ID_NULL : battleMoveGetIdByName(name);
|
||||
if(ability == BATTLE_MOVE_ID_NULL) {
|
||||
errorThrow(
|
||||
"Cutscene item (BATTLE_START): enemies[%u].abilities[%u] is "
|
||||
"invalid",
|
||||
(uint32_t)e, (uint32_t)a
|
||||
);
|
||||
}
|
||||
enemy->abilities[a] = ability;
|
||||
a++;
|
||||
}
|
||||
enemy->abilityCount = (uint8_t)abilityCount;
|
||||
}
|
||||
|
||||
e++;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/battle/fighter/battlefighter.h"
|
||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||
|
||||
// Enemies always occupy the left-side positions (BATTLE_FIGHTER_POS_0-3).
|
||||
#define CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX 4
|
||||
|
||||
typedef struct {
|
||||
battlefighterstats_t stats;
|
||||
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||
uint8_t abilityCount;
|
||||
} cutscenebattlestartenemy_t;
|
||||
|
||||
typedef struct {
|
||||
cutscenebattlestartenemy_t enemies[CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX];
|
||||
uint8_t enemyCount;
|
||||
} cutscenebattlestart_t;
|
||||
|
||||
/**
|
||||
* Starts a BATTLE_START item: spawns the configured enemies, fills the
|
||||
* remaining positions with the active party (via partyGetOrderMember), and
|
||||
* calls battleStart. Always completes immediately - the battle scene takes
|
||||
* over from here.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneBattleStartStart(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates a BATTLE_START item (always completes immediately).
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneBattleStartUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Parses a BATTLE_START 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 cutsceneBattleStartLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
bool_t cutsceneBattleWaitSelectionUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return BATTLE.state != BATTLE_STATE_SELECTION;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
/**
|
||||
* Updates a BATTLE_WAIT_SELECTION step, completing once the battle is no
|
||||
* longer in BATTLE_STATE_SELECTION -- i.e. every fighter has decided a
|
||||
* move (moving on to BATTLE_STATE_EXECUTING) or the selecting fighter has
|
||||
* fled (moving on to BATTLE_STATE_FLED). Has no Start callback -- there's
|
||||
* nothing to do until selection actually finishes.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
* @returns true once BATTLE.state is no longer BATTLE_STATE_SELECTION.
|
||||
*/
|
||||
bool_t cutsceneBattleWaitSelectionUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
);
|
||||
@@ -244,6 +244,27 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.load = cutsceneMapAreaWaitLoad
|
||||
},
|
||||
|
||||
// Battle
|
||||
[CUTSCENE_ITEM_TYPE_BATTLE_START] = {
|
||||
.init = cutsceneBattleStartStart,
|
||||
.update = cutsceneBattleStartUpdate,
|
||||
.jsonName = "BATTLE_START",
|
||||
.load = cutsceneBattleStartLoad
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE] = {
|
||||
.init = cutsceneBattleSetStateStart,
|
||||
.update = cutsceneBattleSetStateUpdate,
|
||||
.jsonName = "BATTLE_SET_STATE",
|
||||
.load = cutsceneBattleSetStateLoad
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION] = {
|
||||
.update = cutsceneBattleWaitSelectionUpdate,
|
||||
.jsonName = "BATTLE_WAIT_SELECTION",
|
||||
.load = cutsceneItemLoadNoop
|
||||
},
|
||||
|
||||
// Save
|
||||
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
|
||||
.init = cutsceneSaveDeviceCheckStart,
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
#include "maparea/cutscenemapareaadd.h"
|
||||
#include "maparea/cutscenemaparearemove.h"
|
||||
#include "maparea/cutscenemapareawait.h"
|
||||
#include "battle/cutscenebattlestart.h"
|
||||
#include "battle/cutscenebattlesetstate.h"
|
||||
#include "battle/cutscenebattlewaitselection.h"
|
||||
#include "save/cutscenesavedevicecheck.h"
|
||||
#include "save/cutscenesaveloadallslots.h"
|
||||
#include "audio/cutsceneaudioplay.h"
|
||||
@@ -112,6 +115,8 @@ struct cutsceneitem_s {
|
||||
cutscenemapareaadd_t mapAreaAdd;
|
||||
cutscenemaparearemove_t mapAreaRemove;
|
||||
cutscenemapareawait_t mapAreaWait;
|
||||
cutscenebattlestart_t battleStart;
|
||||
cutscenebattlesetstate_t battleSetState;
|
||||
cutsceneemoji_t emoji;
|
||||
cutsceneshake_t shake;
|
||||
cutscenemodal_t modal;
|
||||
|
||||
@@ -59,6 +59,11 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
||||
|
||||
// Battle
|
||||
CUTSCENE_ITEM_TYPE_BATTLE_START,
|
||||
CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE,
|
||||
CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION,
|
||||
|
||||
// Save
|
||||
CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,
|
||||
CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS,
|
||||
|
||||
@@ -14,6 +14,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
cutscenejsonemoji.c
|
||||
cutscenejsonentitytype.c
|
||||
cutscenejsonentitydir.c
|
||||
cutscenejsonbattlestate.c
|
||||
cutscenejsonscenetype.c
|
||||
cutscenejsonuiscreentype.c
|
||||
cutscenejsonaudiochannel.c
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutscenejsonbattlestate.h"
|
||||
#include "cutscenejsonenum.h"
|
||||
|
||||
static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_STATE_TABLE[] = {
|
||||
{ "OPENING", BATTLE_STATE_OPENING },
|
||||
{ "PRE_ROUND", BATTLE_STATE_PRE_ROUND },
|
||||
{ "SELECTION", BATTLE_STATE_SELECTION },
|
||||
{ "EXECUTING", BATTLE_STATE_EXECUTING },
|
||||
{ "POST_ROUND", BATTLE_STATE_POST_ROUND },
|
||||
{ "LOST", BATTLE_STATE_LOST },
|
||||
{ "WON", BATTLE_STATE_WON },
|
||||
{ "FLED", BATTLE_STATE_FLED },
|
||||
{ "ENDING", BATTLE_STATE_ENDING },
|
||||
};
|
||||
#define CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT \
|
||||
(sizeof(CUTSCENE_JSON_BATTLE_STATE_TABLE) / sizeof(cutscenejsonenumentry_t))
|
||||
|
||||
bool_t cutsceneJsonLookupBattleState(
|
||||
const char_t *name,
|
||||
battlestate_t *out
|
||||
) {
|
||||
int32_t value;
|
||||
if(!cutsceneJsonLookupEnumInsensitive(
|
||||
CUTSCENE_JSON_BATTLE_STATE_TABLE,
|
||||
CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT, name, &value
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
*out = (battlestate_t)value;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/battle/state/battlestate.h"
|
||||
|
||||
/**
|
||||
* Looks up name (case-insensitive) against every battlestate_t name.
|
||||
*
|
||||
* @param name Name to search for.
|
||||
* @param out Set to the matching state on success.
|
||||
* @return true if a matching entry was found, false otherwise.
|
||||
*/
|
||||
bool_t cutsceneJsonLookupBattleState(
|
||||
const char_t *name,
|
||||
battlestate_t *out
|
||||
);
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "scenebattle.h"
|
||||
#include "assert/assert.h"
|
||||
#include "display/display.h"
|
||||
#include "display/displaystate.h"
|
||||
#include "display/shader/shader.h"
|
||||
@@ -80,6 +81,10 @@ void sceneBattleWorldToScreen(
|
||||
vec3 renderPos,
|
||||
vec2 out
|
||||
) {
|
||||
assertNotNull(battle, "battle cannot be NULL");
|
||||
assertNotNull(renderPos, "renderPos cannot be NULL");
|
||||
assertNotNull(out, "out cannot be NULL");
|
||||
|
||||
mat4 viewProj;
|
||||
glm_mat4_mul(battle->projection, battle->eye, viewProj);
|
||||
|
||||
@@ -94,15 +99,20 @@ void sceneBattleWorldToScreen(
|
||||
}
|
||||
|
||||
void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
||||
assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds");
|
||||
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||
assertNotNull(fighter, "Fighter cannot be NULL");
|
||||
assertNotNull(out, "out cannot be NULL");
|
||||
|
||||
// Ordinal (and count) of this fighter among its living-slot teammates,
|
||||
// in slot order, so each team lays out as an evenly spaced row.
|
||||
uint8_t ordinal = 0;
|
||||
uint8_t teamCount = 0;
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||
if(BATTLE.fighters[i]->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||
if(BATTLE.fighters[i]->team != fighter->team) continue;
|
||||
const battlefighter_t *candidate = BATTLE.fighters[i];
|
||||
if(candidate == NULL) continue;
|
||||
if(candidate->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||
if(candidate->team != fighter->team) continue;
|
||||
if(i < fighterIndex) ordinal++;
|
||||
teamCount++;
|
||||
}
|
||||
@@ -119,7 +129,9 @@ void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
||||
}
|
||||
|
||||
errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) {
|
||||
assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds");
|
||||
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||
if(fighter == NULL) errorOk();
|
||||
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk();
|
||||
|
||||
vec3 position;
|
||||
|
||||
@@ -42,12 +42,13 @@ errorret_t uiBattleHudInit(void) {
|
||||
}
|
||||
|
||||
errorret_t uiBattleHudDraw(void) {
|
||||
if(BATTLE.state == BATTLE_STATE_NONE) errorOk();
|
||||
if(BATTLE.state == BATTLE_STATE_NULL) errorOk();
|
||||
|
||||
scenebattle_t *battle = &SCENE.data.battle;
|
||||
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||
const battlefighter_t *fighter = BATTLE.fighters[i];
|
||||
if(fighter == NULL) continue;
|
||||
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||
|
||||
vec3 position;
|
||||
|
||||
@@ -22,13 +22,14 @@ void uiBattleMenuTargetSelected(
|
||||
const uint8_t index,
|
||||
const uimenuitem_t *item
|
||||
) {
|
||||
battlePlayerAttack(UI_BATTLE_MENU.targetFighterIndex[index]);
|
||||
assertTrue(index < UI_BATTLE_MENU_TARGET_ITEM_COUNT, "index out of bounds");
|
||||
battleStateSelectionPlayerAttack(UI_BATTLE_MENU.targetFighterIndex[index]);
|
||||
uiMenuClose(&UI_BATTLE_MENU.targetMenu);
|
||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
||||
}
|
||||
|
||||
void uiBattleMenuOpenTargets(void) {
|
||||
battlefighter_t *current = battleGetCurrentFighter();
|
||||
battlefighter_t *current = battleStateSelectionGetCurrentFighter();
|
||||
if(current == NULL) return;
|
||||
|
||||
const battlefighterteam_t enemyTeam =
|
||||
@@ -42,6 +43,7 @@ void uiBattleMenuOpenTargets(void) {
|
||||
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||
battlefighter_t *candidate = BATTLE.fighters[i];
|
||||
if(candidate == NULL) continue;
|
||||
if(candidate->team != enemyTeam) continue;
|
||||
if(!battleFighterIsAlive(candidate)) continue;
|
||||
|
||||
@@ -71,7 +73,7 @@ void uiBattleMenuActionSelected(
|
||||
break;
|
||||
|
||||
case UI_BATTLE_MENU_ACTION_INDEX_FLEE:
|
||||
battlePlayerFlee();
|
||||
battleStateSelectionPlayerFlee();
|
||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
||||
break;
|
||||
|
||||
@@ -116,7 +118,7 @@ errorret_t uiBattleMenuInit(void) {
|
||||
errorret_t uiBattleMenuUpdate(void) {
|
||||
if(uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk();
|
||||
|
||||
battlefighter_t *current = battleGetCurrentFighter();
|
||||
battlefighter_t *current = battleStateSelectionGetCurrentFighter();
|
||||
if(current == NULL) errorOk();
|
||||
if(current->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) errorOk();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user