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:
@@ -1,24 +1,22 @@
|
|||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"type": "START_BATTLE",
|
"type": "BATTLE_START",
|
||||||
"encounterType": "REGULAR",
|
|
||||||
"fleeAvailable": true,
|
|
||||||
"enemies": [
|
"enemies": [
|
||||||
{
|
{
|
||||||
"stats": { "attack": 8, "defense": 4, "magic": 0, "speed": 5, "luck": 2 },
|
"stats": {
|
||||||
"healthMax": 30,
|
"attack": 8, "defense": 4, "magic": 0, "speed": 5, "luck": 2,
|
||||||
"mpMax": 0
|
"healthMax": 30, "mpMax": 0
|
||||||
|
},
|
||||||
|
"abilities": ["fire"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"stats": { "attack": 6, "defense": 3, "magic": 0, "speed": 7, "luck": 2 },
|
"stats": {
|
||||||
"healthMax": 20,
|
"attack": 6, "defense": 3, "magic": 0, "speed": 7, "luck": 2,
|
||||||
"mpMax": 0
|
"healthMax": 20, "mpMax": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "REGULAR_BATTLE"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
#include "scene/scene.h"
|
||||||
|
|
||||||
battle_t BATTLE;
|
battle_t BATTLE;
|
||||||
|
|
||||||
@@ -22,7 +23,8 @@ void battleStart(
|
|||||||
assertNotNull(fighters, "Fighters cannot be NULL");
|
assertNotNull(fighters, "Fighters cannot be NULL");
|
||||||
|
|
||||||
#if DUSK_ASSERTIONS
|
#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;
|
uint8_t nonNullCount = 0;
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
if(fighters[i] != NULL) nonNullCount++;
|
if(fighters[i] != NULL) nonNullCount++;
|
||||||
@@ -39,18 +41,38 @@ void battleStart(
|
|||||||
sizeof(battlefighter_t *) * BATTLE_FIGHTER_POS_COUNT
|
sizeof(battlefighter_t *) * BATTLE_FIGHTER_POS_COUNT
|
||||||
);
|
);
|
||||||
|
|
||||||
// Call init on opening state.
|
// Enter the opening state.
|
||||||
BATTLE.state = BATTLE_STATE_OPENING;
|
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();
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleUpdate(void) {
|
void battleUpdate(void) {
|
||||||
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
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();
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleDispose(void) {
|
void battleDispose(void) {
|
||||||
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
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();
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose();
|
||||||
memoryZero(&BATTLE, sizeof(battle_t));
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ void battleStart(
|
|||||||
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]
|
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.
|
* Updates the battle system.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "battlemove.h"
|
#include "battlemove.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "asset/loader/assetloader.h"
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -17,7 +18,7 @@ uint8_t BATTLE_MOVE_COUNT;
|
|||||||
|
|
||||||
errorret_t battleMoveInit(void) {
|
errorret_t battleMoveInit(void) {
|
||||||
memoryZero(BATTLE_MOVES, sizeof(BATTLE_MOVES));
|
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.
|
// Get moves JSON.
|
||||||
assetentry_t *entry = assetLock(
|
assetentry_t *entry = assetLock(
|
||||||
@@ -126,3 +127,13 @@ errorret_t battleMoveInit(void) {
|
|||||||
assetUnlockEntry(entry);
|
assetUnlockEntry(entry);
|
||||||
errorOk();
|
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.
|
* @return Any error that occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t battleMoveInit(void);
|
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 "battlestateopening.h"
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
void battleStateOpeningInit(void) {
|
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) {
|
void battleStateOpeningUpdate(void) {
|
||||||
|
|||||||
@@ -6,9 +6,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "battlestatepreround.h"
|
#include "battlestatepreround.h"
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
void battleStatePreRoundInit(void) {
|
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) {
|
void battleStatePreRoundUpdate(void) {
|
||||||
|
|||||||
@@ -6,15 +6,114 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "battlestateselection.h"
|
#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) {
|
void battleStateSelectionInit(void) {
|
||||||
|
BATTLE.stateData.selection.selectionIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleStateSelectionUpdate(void) {
|
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) {
|
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
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "rpg/battle/fighter/battlefighter.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
// Cursor into BATTLE.fighters (by position index) for the fighter
|
||||||
|
// currently deciding a move this round.
|
||||||
|
uint8_t selectionIndex;
|
||||||
} battlestateselection_t;
|
} battlestateselection_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +20,11 @@ typedef struct {
|
|||||||
void battleStateSelectionInit(void);
|
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);
|
void battleStateSelectionUpdate(void);
|
||||||
|
|
||||||
@@ -26,3 +32,49 @@ void battleStateSelectionUpdate(void);
|
|||||||
* Called when the battle leaves BATTLE_STATE_SELECTION.
|
* Called when the battle leaves BATTLE_STATE_SELECTION.
|
||||||
*/
|
*/
|
||||||
void battleStateSelectionDispose(void);
|
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);
|
cutsceneGoTo(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
|
void cutsceneSystemInsertItemsNext(
|
||||||
assertNotNull(cutscene, "Cutscene cannot be NULL");
|
const cutsceneitem_t *items,
|
||||||
|
const size_t itemCount
|
||||||
|
) {
|
||||||
|
assertNotNull(items, "Items cannot be NULL");
|
||||||
assertNotNull(
|
assertNotNull(
|
||||||
CUTSCENE_SYSTEM.scene,
|
CUTSCENE_SYSTEM.scene,
|
||||||
"cutsceneSystemInsertCutscene called with no cutscene running"
|
"cutsceneSystemInsertItemsNext called with no cutscene running"
|
||||||
);
|
);
|
||||||
|
|
||||||
cutsceneAppendNext(
|
cutsceneAppendNext(
|
||||||
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem,
|
CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem, items, itemCount
|
||||||
cutscene->items, cutscene->itemCount
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cutsceneSystemInsertItems(
|
||||||
|
const cutsceneitem_t *items,
|
||||||
|
const size_t itemCount
|
||||||
|
) {
|
||||||
|
cutsceneSystemInsertItemsNext(items, itemCount);
|
||||||
cutsceneSystemNext();
|
cutsceneSystemNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) {
|
||||||
|
assertNotNull(cutscene, "Cutscene cannot be NULL");
|
||||||
|
cutsceneSystemInsertItems(cutscene->items, cutscene->itemCount);
|
||||||
|
}
|
||||||
|
|
||||||
void cutsceneSystemLoad(const char_t *file) {
|
void cutsceneSystemLoad(const char_t *file) {
|
||||||
assertNotNull(file, "File cannot be NULL");
|
assertNotNull(file, "File cannot be NULL");
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,32 @@ void cutsceneSystemStartCutsceneAndGoToMarker(
|
|||||||
const char_t *marker
|
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.
|
* Splices cutscene's items into the running cutscene.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ add_subdirectory(entity)
|
|||||||
add_subdirectory(item)
|
add_subdirectory(item)
|
||||||
add_subdirectory(maparea)
|
add_subdirectory(maparea)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(battle)
|
||||||
add_subdirectory(save)
|
add_subdirectory(save)
|
||||||
add_subdirectory(audio)
|
add_subdirectory(audio)
|
||||||
add_subdirectory(json)
|
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
|
.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
|
// Save
|
||||||
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
|
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
|
||||||
.init = cutsceneSaveDeviceCheckStart,
|
.init = cutsceneSaveDeviceCheckStart,
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
#include "maparea/cutscenemapareaadd.h"
|
#include "maparea/cutscenemapareaadd.h"
|
||||||
#include "maparea/cutscenemaparearemove.h"
|
#include "maparea/cutscenemaparearemove.h"
|
||||||
#include "maparea/cutscenemapareawait.h"
|
#include "maparea/cutscenemapareawait.h"
|
||||||
|
#include "battle/cutscenebattlestart.h"
|
||||||
|
#include "battle/cutscenebattlesetstate.h"
|
||||||
|
#include "battle/cutscenebattlewaitselection.h"
|
||||||
#include "save/cutscenesavedevicecheck.h"
|
#include "save/cutscenesavedevicecheck.h"
|
||||||
#include "save/cutscenesaveloadallslots.h"
|
#include "save/cutscenesaveloadallslots.h"
|
||||||
#include "audio/cutsceneaudioplay.h"
|
#include "audio/cutsceneaudioplay.h"
|
||||||
@@ -112,6 +115,8 @@ struct cutsceneitem_s {
|
|||||||
cutscenemapareaadd_t mapAreaAdd;
|
cutscenemapareaadd_t mapAreaAdd;
|
||||||
cutscenemaparearemove_t mapAreaRemove;
|
cutscenemaparearemove_t mapAreaRemove;
|
||||||
cutscenemapareawait_t mapAreaWait;
|
cutscenemapareawait_t mapAreaWait;
|
||||||
|
cutscenebattlestart_t battleStart;
|
||||||
|
cutscenebattlesetstate_t battleSetState;
|
||||||
cutsceneemoji_t emoji;
|
cutsceneemoji_t emoji;
|
||||||
cutsceneshake_t shake;
|
cutsceneshake_t shake;
|
||||||
cutscenemodal_t modal;
|
cutscenemodal_t modal;
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||||
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
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
|
// Save
|
||||||
CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,
|
CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK,
|
||||||
CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS,
|
CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
cutscenejsonemoji.c
|
cutscenejsonemoji.c
|
||||||
cutscenejsonentitytype.c
|
cutscenejsonentitytype.c
|
||||||
cutscenejsonentitydir.c
|
cutscenejsonentitydir.c
|
||||||
|
cutscenejsonbattlestate.c
|
||||||
cutscenejsonscenetype.c
|
cutscenejsonscenetype.c
|
||||||
cutscenejsonuiscreentype.c
|
cutscenejsonuiscreentype.c
|
||||||
cutscenejsonaudiochannel.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 "scenebattle.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include "display/displaystate.h"
|
#include "display/displaystate.h"
|
||||||
#include "display/shader/shader.h"
|
#include "display/shader/shader.h"
|
||||||
@@ -80,6 +81,10 @@ void sceneBattleWorldToScreen(
|
|||||||
vec3 renderPos,
|
vec3 renderPos,
|
||||||
vec2 out
|
vec2 out
|
||||||
) {
|
) {
|
||||||
|
assertNotNull(battle, "battle cannot be NULL");
|
||||||
|
assertNotNull(renderPos, "renderPos cannot be NULL");
|
||||||
|
assertNotNull(out, "out cannot be NULL");
|
||||||
|
|
||||||
mat4 viewProj;
|
mat4 viewProj;
|
||||||
glm_mat4_mul(battle->projection, battle->eye, viewProj);
|
glm_mat4_mul(battle->projection, battle->eye, viewProj);
|
||||||
|
|
||||||
@@ -94,15 +99,20 @@ void sceneBattleWorldToScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
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];
|
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,
|
// Ordinal (and count) of this fighter among its living-slot teammates,
|
||||||
// in slot order, so each team lays out as an evenly spaced row.
|
// in slot order, so each team lays out as an evenly spaced row.
|
||||||
uint8_t ordinal = 0;
|
uint8_t ordinal = 0;
|
||||||
uint8_t teamCount = 0;
|
uint8_t teamCount = 0;
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
if(BATTLE.fighters[i]->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
const battlefighter_t *candidate = BATTLE.fighters[i];
|
||||||
if(BATTLE.fighters[i]->team != fighter->team) continue;
|
if(candidate == NULL) continue;
|
||||||
|
if(candidate->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||||
|
if(candidate->team != fighter->team) continue;
|
||||||
if(i < fighterIndex) ordinal++;
|
if(i < fighterIndex) ordinal++;
|
||||||
teamCount++;
|
teamCount++;
|
||||||
}
|
}
|
||||||
@@ -119,7 +129,9 @@ void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) {
|
errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) {
|
||||||
|
assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds");
|
||||||
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||||
|
if(fighter == NULL) errorOk();
|
||||||
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk();
|
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk();
|
||||||
|
|
||||||
vec3 position;
|
vec3 position;
|
||||||
|
|||||||
@@ -42,12 +42,13 @@ errorret_t uiBattleHudInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t uiBattleHudDraw(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;
|
scenebattle_t *battle = &SCENE.data.battle;
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
const battlefighter_t *fighter = BATTLE.fighters[i];
|
const battlefighter_t *fighter = BATTLE.fighters[i];
|
||||||
|
if(fighter == NULL) continue;
|
||||||
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||||
|
|
||||||
vec3 position;
|
vec3 position;
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ void uiBattleMenuTargetSelected(
|
|||||||
const uint8_t index,
|
const uint8_t index,
|
||||||
const uimenuitem_t *item
|
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.targetMenu);
|
||||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiBattleMenuOpenTargets(void) {
|
void uiBattleMenuOpenTargets(void) {
|
||||||
battlefighter_t *current = battleGetCurrentFighter();
|
battlefighter_t *current = battleStateSelectionGetCurrentFighter();
|
||||||
if(current == NULL) return;
|
if(current == NULL) return;
|
||||||
|
|
||||||
const battlefighterteam_t enemyTeam =
|
const battlefighterteam_t enemyTeam =
|
||||||
@@ -42,6 +43,7 @@ void uiBattleMenuOpenTargets(void) {
|
|||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
battlefighter_t *candidate = BATTLE.fighters[i];
|
battlefighter_t *candidate = BATTLE.fighters[i];
|
||||||
|
if(candidate == NULL) continue;
|
||||||
if(candidate->team != enemyTeam) continue;
|
if(candidate->team != enemyTeam) continue;
|
||||||
if(!battleFighterIsAlive(candidate)) continue;
|
if(!battleFighterIsAlive(candidate)) continue;
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ void uiBattleMenuActionSelected(
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case UI_BATTLE_MENU_ACTION_INDEX_FLEE:
|
case UI_BATTLE_MENU_ACTION_INDEX_FLEE:
|
||||||
battlePlayerFlee();
|
battleStateSelectionPlayerFlee();
|
||||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ errorret_t uiBattleMenuInit(void) {
|
|||||||
errorret_t uiBattleMenuUpdate(void) {
|
errorret_t uiBattleMenuUpdate(void) {
|
||||||
if(uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk();
|
if(uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk();
|
||||||
|
|
||||||
battlefighter_t *current = battleGetCurrentFighter();
|
battlefighter_t *current = battleStateSelectionGetCurrentFighter();
|
||||||
if(current == NULL) errorOk();
|
if(current == NULL) errorOk();
|
||||||
if(current->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) errorOk();
|
if(current->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) errorOk();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user