Rework battle system: pointer-based fighters, per-state files, move split
Splits battlemoveaction_t into its own header, adds a per-state struct/init/update/dispose file under rpg/battle/state driven by a dispatch table, switches BATTLE.fighters to battlefighter_t* backed by a pool, folds current/max health and mp into battlefighterstats_t, and adds battleFighterDamage/Heal/AbilityAdd. Removes the now-unused battle cutscene items (START_BATTLE, BATTLE_WAIT_STATE, BATTLE_FORCE_MOVE, REGULAR_BATTLE) and their JSON lookup helpers, and adds JSON validation to battleMoveInit. battle.h/battle.c are mid-rewrite around the new state machine, so some UI consumers (uibattlehud.c, uibattlemenu.c) and test_battle.c are currently out of sync with the new API. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -12,3 +12,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
|
|
||||||
add_subdirectory(fighter)
|
add_subdirectory(fighter)
|
||||||
add_subdirectory(move)
|
add_subdirectory(move)
|
||||||
|
add_subdirectory(state)
|
||||||
|
|||||||
+25
-296
@@ -10,319 +10,48 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
|
|
||||||
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
|
|
||||||
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
|
|
||||||
|
|
||||||
battle_t BATTLE;
|
battle_t BATTLE;
|
||||||
|
|
||||||
void battleInit(void) {
|
void battleInit(void) {
|
||||||
memoryZero(&BATTLE, sizeof(battle_t));
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
BATTLE.fighters[i].id = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t battleGetAvailableFighter(void) {
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(BATTLE.fighters[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleAddFighter(
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
|
||||||
const uint8_t index = battleGetAvailableFighter();
|
|
||||||
if(index == 0xFF) return NULL;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[index];
|
|
||||||
battleFighterInit(fighter, team, controller, stats, healthMax, mpMax);
|
|
||||||
return fighter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleStart(
|
void battleStart(
|
||||||
const battleencountertype_t encounterType,
|
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]
|
||||||
const bool_t fleeAvailable
|
|
||||||
) {
|
) {
|
||||||
assertTrue(encounterType < BATTLE_ENCOUNTER_COUNT, "Invalid encounter type");
|
assertNotNull(fighters, "Fighters cannot be NULL");
|
||||||
|
|
||||||
BATTLE.encounterType = encounterType;
|
#if DUSK_ASSERTIONS
|
||||||
BATTLE.fleeAvailable = fleeAvailable;
|
// Must be at least two non null fighter present.
|
||||||
BATTLE.result = BATTLE_RESULT_NONE;
|
uint8_t nonNullCount = 0;
|
||||||
BATTLE.round = 1;
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
|
if(fighters[i] != NULL) nonNullCount++;
|
||||||
battleSetState(BATTLE_STATE_OPENING);
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleDispose(void) {
|
|
||||||
battleInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleGetCurrentFighter(void) {
|
|
||||||
if(BATTLE.state != BATTLE_STATE_PLAYER_SELECTION) return NULL;
|
|
||||||
if(BATTLE.selectionIndex >= BATTLE.executionCount) return NULL;
|
|
||||||
return &BATTLE.fighters[BATTLE.executionOrder[BATTLE.selectionIndex]];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t battleGetAliveCount(const battlefighterteam_t team) {
|
|
||||||
uint8_t count = 0;
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(BATTLE.fighters[i].team != team) continue;
|
|
||||||
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
return count;
|
assertTrue(nonNullCount >= 2, "Must have at least two fighters");
|
||||||
}
|
#endif
|
||||||
|
|
||||||
void battleResolveAttack(
|
|
||||||
battlefighter_t *attacker,
|
|
||||||
battlefighter_t *defender
|
|
||||||
) {
|
|
||||||
assertNotNull(attacker, "Attacker cannot be NULL");
|
|
||||||
assertNotNull(defender, "Defender cannot be NULL");
|
|
||||||
|
|
||||||
const int32_t rawDamage =
|
// TODO: I may not need to zero entire battle.
|
||||||
(int32_t)attacker->stats.attack - (int32_t)defender->stats.defense;
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
const uint16_t damage = rawDamage > 0 ? (uint16_t)rawDamage : 1;
|
memoryCopy(
|
||||||
|
BATTLE.fighters,
|
||||||
|
fighters,
|
||||||
|
sizeof(battlefighter_t *) * BATTLE_FIGHTER_POS_COUNT
|
||||||
|
);
|
||||||
|
|
||||||
defender->health = damage >= defender->health ? 0 : defender->health - damage;
|
// Call init on opening state.
|
||||||
if(defender->health == 0) defender->status = BATTLE_FIGHTER_STATUS_DEAD;
|
BATTLE.state = BATTLE_STATE_OPENING;
|
||||||
}
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].init();
|
||||||
|
|
||||||
battleresult_t battleCheckResult(void) {
|
|
||||||
if(BATTLE.result != BATTLE_RESULT_NONE) return BATTLE.result;
|
|
||||||
|
|
||||||
if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ALLY) == 0) {
|
|
||||||
battleSetResult(BATTLE_RESULT_LOSS);
|
|
||||||
} else if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ENEMY) == 0) {
|
|
||||||
battleSetResult(BATTLE_RESULT_WIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
return BATTLE.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleQueueMove(
|
|
||||||
const uint8_t fighterIndex,
|
|
||||||
const battlemoveid_t move,
|
|
||||||
const uint8_t targetIndex
|
|
||||||
) {
|
|
||||||
battlemoveaction_t *action = &BATTLE.moves[fighterIndex];
|
|
||||||
action->move = move;
|
|
||||||
action->targetIndex = targetIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battlePlayerAttack(const uint8_t targetIndex) {
|
|
||||||
battlefighter_t *fighter = battleGetCurrentFighter();
|
|
||||||
if(fighter == NULL) return;
|
|
||||||
if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return;
|
|
||||||
if(!battleFighterIsAlive(&BATTLE.fighters[targetIndex])) return;
|
|
||||||
|
|
||||||
battleQueueMove(fighter->id, BATTLE_MOVE_ID_ATTACK, targetIndex);
|
|
||||||
BATTLE.selectionIndex++;
|
|
||||||
battleAdvanceSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
void battlePlayerFlee(void) {
|
|
||||||
battlefighter_t *fighter = battleGetCurrentFighter();
|
|
||||||
if(fighter == NULL) return;
|
|
||||||
if(!BATTLE.fleeAvailable) return;
|
|
||||||
|
|
||||||
battleSetResult(BATTLE_RESULT_FLED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleUpdate(void) {
|
void battleUpdate(void) {
|
||||||
if(BATTLE.state == BATTLE_STATE_NONE) return;
|
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
||||||
if(BATTLE.state == BATTLE_STATE_ENDED) return;
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].update();
|
||||||
if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE) return;
|
|
||||||
|
|
||||||
switch(BATTLE.state) {
|
|
||||||
case BATTLE_STATE_OPENING:
|
|
||||||
battleSetState(BATTLE_STATE_PRE_ROUND);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BATTLE_STATE_PRE_ROUND:
|
|
||||||
battleUpdatePreRound();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BATTLE_STATE_AI_SELECTION:
|
|
||||||
battleUpdateAiSelection();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BATTLE_STATE_MOVES_EXECUTING:
|
|
||||||
battleUpdateMovesExecuting();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BATTLE_STATE_POST_ROUND:
|
|
||||||
battleUpdatePostRound();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// BATTLE_STATE_PLAYER_SELECTION: waits on battlePlayerAttack/Flee.
|
|
||||||
// BATTLE_STATE_NONE/ENDED: handled above.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void battleBuildExecutionOrder(const bool_t applyEncounterBias) {
|
void battleDispose(void) {
|
||||||
BATTLE.executionCount = 0;
|
if(BATTLE.state == BATTLE_STATE_NULL) return;
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
|
||||||
BATTLE.executionOrder[BATTLE.executionCount++] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insertion sort by speed descending -- fine for BATTLE_FIGHTER_COUNT_MAX.
|
BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose();
|
||||||
for(uint8_t i = 1; i < BATTLE.executionCount; i++) {
|
memoryZero(&BATTLE, sizeof(battle_t));
|
||||||
const uint8_t key = BATTLE.executionOrder[i];
|
|
||||||
const uint16_t keySpeed = BATTLE.fighters[key].stats.speed;
|
|
||||||
|
|
||||||
int8_t j = (int8_t)i - 1;
|
|
||||||
while(
|
|
||||||
j >= 0 &&
|
|
||||||
BATTLE.fighters[BATTLE.executionOrder[j]].stats.speed < keySpeed
|
|
||||||
) {
|
|
||||||
BATTLE.executionOrder[j + 1] = BATTLE.executionOrder[j];
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
BATTLE.executionOrder[j + 1] = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!applyEncounterBias) return;
|
|
||||||
|
|
||||||
if(BATTLE.encounterType == BATTLE_ENCOUNTER_PLAYER_ADVANTAGE) {
|
|
||||||
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ALLY);
|
|
||||||
} else if(BATTLE.encounterType == BATTLE_ENCOUNTER_BACK_ATTACK) {
|
|
||||||
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ENEMY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleMoveTeamFirst(const battlefighterteam_t team) {
|
|
||||||
uint8_t sorted[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
uint8_t count = 0;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
|
||||||
if(BATTLE.fighters[BATTLE.executionOrder[i]].team != team) continue;
|
|
||||||
sorted[count++] = BATTLE.executionOrder[i];
|
|
||||||
}
|
|
||||||
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
|
||||||
if(BATTLE.fighters[BATTLE.executionOrder[i]].team == team) continue;
|
|
||||||
sorted[count++] = BATTLE.executionOrder[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryCopy(
|
|
||||||
BATTLE.executionOrder, sorted, sizeof(uint8_t) * BATTLE.executionCount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
|
|
||||||
const battlefighterteam_t enemyTeam = (
|
|
||||||
fighter->team == BATTLE_FIGHTER_TEAM_ALLY ?
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY :
|
|
||||||
BATTLE_FIGHTER_TEAM_ALLY
|
|
||||||
);
|
|
||||||
|
|
||||||
battlefighter_t *weakest = NULL;
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
battlefighter_t *candidate = &BATTLE.fighters[i];
|
|
||||||
if(candidate->team != enemyTeam) continue;
|
|
||||||
if(!battleFighterIsAlive(candidate)) continue;
|
|
||||||
if(weakest == NULL || candidate->health < weakest->health) {
|
|
||||||
weakest = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return weakest;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleSetState(const battlestate_t next) {
|
|
||||||
BATTLE.state = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleSetResult(const battleresult_t result) {
|
|
||||||
BATTLE.result = result;
|
|
||||||
battleSetState(BATTLE_STATE_ENDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t battleFighterNeedsDecision(
|
|
||||||
const uint8_t fighterIndex,
|
|
||||||
const battlefightercontroller_t controller
|
|
||||||
) {
|
|
||||||
return battleFighterIsAlive(&BATTLE.fighters[fighterIndex])
|
|
||||||
&& BATTLE.fighters[fighterIndex].controller == controller
|
|
||||||
&& BATTLE.moves[fighterIndex].move == BATTLE_MOVE_ID_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleAdvanceSelection(void) {
|
|
||||||
while(BATTLE.selectionIndex < BATTLE.executionCount) {
|
|
||||||
const uint8_t fighterIndex = BATTLE.executionOrder[BATTLE.selectionIndex];
|
|
||||||
if(
|
|
||||||
battleFighterNeedsDecision(fighterIndex, BATTLE_FIGHTER_CONTROLLER_PLAYER)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BATTLE.selectionIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
battleSetState(BATTLE_STATE_AI_SELECTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleUpdatePreRound(void) {
|
|
||||||
// No need to clear BATTLE.moves here: every living fighter's move is
|
|
||||||
// unconditionally reset to BATTLE_MOVE_ID_NULL as it's processed in
|
|
||||||
// battleUpdateMovesExecuting, and round 1 starts pre-zeroed by
|
|
||||||
// battleInit(). Clearing it here would also wipe out any move a
|
|
||||||
// cutscene force-queued while parked at BATTLE_STATE_PRE_ROUND.
|
|
||||||
battleBuildExecutionOrder(BATTLE.round == 1);
|
|
||||||
|
|
||||||
BATTLE.selectionIndex = 0;
|
|
||||||
battleSetState(BATTLE_STATE_PLAYER_SELECTION);
|
|
||||||
battleAdvanceSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleUpdateAiSelection(void) {
|
|
||||||
for(uint8_t i = 0; i < BATTLE.executionCount; i++) {
|
|
||||||
const uint8_t fighterIndex = BATTLE.executionOrder[i];
|
|
||||||
if(
|
|
||||||
!battleFighterNeedsDecision(fighterIndex, BATTLE_FIGHTER_CONTROLLER_AI)
|
|
||||||
) continue;
|
|
||||||
|
|
||||||
battlefighter_t *target =
|
|
||||||
battleAIChooseTarget(&BATTLE.fighters[fighterIndex]);
|
|
||||||
if(target == NULL) continue;
|
|
||||||
|
|
||||||
battleQueueMove(fighterIndex, BATTLE_MOVE_ID_ATTACK, target->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
BATTLE.executionIndex = 0;
|
|
||||||
battleSetState(BATTLE_STATE_MOVES_EXECUTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleUpdateMovesExecuting(void) {
|
|
||||||
if(BATTLE.executionIndex >= BATTLE.executionCount) {
|
|
||||||
battleSetState(BATTLE_STATE_POST_ROUND);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t fighterIndex = BATTLE.executionOrder[BATTLE.executionIndex++];
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
|
|
||||||
if(!battleFighterIsAlive(fighter)) return;
|
|
||||||
|
|
||||||
battlemoveaction_t *action = &BATTLE.moves[fighterIndex];
|
|
||||||
if(action->move == BATTLE_MOVE_ID_ATTACK) {
|
|
||||||
battlefighter_t *target = &BATTLE.fighters[action->targetIndex];
|
|
||||||
if(battleFighterIsAlive(target)) battleResolveAttack(fighter, target);
|
|
||||||
}
|
|
||||||
action->move = BATTLE_MOVE_ID_NULL;
|
|
||||||
|
|
||||||
battleCheckResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleUpdatePostRound(void) {
|
|
||||||
BATTLE.round++;
|
|
||||||
battleSetState(BATTLE_STATE_PRE_ROUND);
|
|
||||||
}
|
}
|
||||||
+27
-250
@@ -8,59 +8,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/battle/fighter/battlefighter.h"
|
#include "rpg/battle/fighter/battlefighter.h"
|
||||||
#include "rpg/battle/move/battlemove.h"
|
#include "rpg/battle/move/battlemove.h"
|
||||||
|
#include "rpg/battle/move/battlemoveaction.h"
|
||||||
|
#include "rpg/battle/fighter/battlefighterpos.h"
|
||||||
|
#include "rpg/battle/state/battlestate.h"
|
||||||
|
|
||||||
#define BATTLE_FIGHTER_COUNT_MAX 8
|
// typedef enum {
|
||||||
|
// BATTLE_RESULT_NONE,
|
||||||
typedef enum {
|
// BATTLE_RESULT_WIN,
|
||||||
BATTLE_ENCOUNTER_REGULAR,
|
// BATTLE_RESULT_LOSS,
|
||||||
BATTLE_ENCOUNTER_PLAYER_ADVANTAGE,
|
// BATTLE_RESULT_FLED,
|
||||||
BATTLE_ENCOUNTER_BACK_ATTACK,
|
// BATTLE_RESULT_COUNT
|
||||||
|
// } battleresult_t;
|
||||||
BATTLE_ENCOUNTER_COUNT
|
|
||||||
} battleencountertype_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
BATTLE_RESULT_NONE,
|
|
||||||
BATTLE_RESULT_WIN,
|
|
||||||
BATTLE_RESULT_LOSS,
|
|
||||||
BATTLE_RESULT_FLED,
|
|
||||||
|
|
||||||
BATTLE_RESULT_COUNT
|
|
||||||
} battleresult_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
BATTLE_STATE_NONE,
|
|
||||||
|
|
||||||
BATTLE_STATE_OPENING,
|
|
||||||
BATTLE_STATE_PRE_ROUND,
|
|
||||||
BATTLE_STATE_PLAYER_SELECTION,
|
|
||||||
BATTLE_STATE_AI_SELECTION,
|
|
||||||
BATTLE_STATE_MOVES_EXECUTING,
|
|
||||||
BATTLE_STATE_POST_ROUND,
|
|
||||||
|
|
||||||
BATTLE_STATE_ENDED,
|
|
||||||
|
|
||||||
BATTLE_STATE_COUNT
|
|
||||||
} battlestate_t;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT];
|
||||||
|
|
||||||
|
// Order of who gets to act.
|
||||||
|
uint8_t fighterOrder[BATTLE_FIGHTER_POS_COUNT];
|
||||||
|
|
||||||
|
// Actions to be executed.
|
||||||
|
battlemoveaction_t actionsPending[BATTLE_FIGHTER_POS_COUNT];
|
||||||
|
|
||||||
battlestate_t state;
|
battlestate_t state;
|
||||||
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
battlestatedata_t stateData;
|
||||||
battlemoveaction_t moves[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
|
|
||||||
battleencountertype_t encounterType;
|
|
||||||
bool_t fleeAvailable;
|
|
||||||
battleresult_t result;
|
|
||||||
|
|
||||||
// Fighter indices (into fighters[]), sorted for the current round.
|
|
||||||
uint8_t executionOrder[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
uint8_t executionCount;
|
|
||||||
uint8_t executionIndex;
|
|
||||||
uint16_t round;
|
|
||||||
|
|
||||||
// Cursor into executionOrder used by BATTLE_STATE_PLAYER_SELECTION to find
|
|
||||||
// the next player-controlled fighter that still needs a decision.
|
|
||||||
uint8_t selectionIndex;
|
|
||||||
} battle_t;
|
} battle_t;
|
||||||
|
|
||||||
extern battle_t BATTLE;
|
extern battle_t BATTLE;
|
||||||
@@ -71,213 +41,20 @@ extern battle_t BATTLE;
|
|||||||
void battleInit(void);
|
void battleInit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an available (unused) fighter slot index.
|
* Starts a battle.
|
||||||
*
|
*
|
||||||
* @return The index of an available fighter slot, or 0xFF if none are
|
* @param fighters Fighters in the battle.
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
uint8_t battleGetAvailableFighter(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a fighter to the battle in the next available slot.
|
|
||||||
*
|
|
||||||
* @param team The team the fighter belongs to.
|
|
||||||
* @param controller Who makes decisions for the fighter.
|
|
||||||
* @param stats The fighter's base combat stats.
|
|
||||||
* @param healthMax The fighter's maximum health.
|
|
||||||
* @param mpMax The fighter's maximum mp.
|
|
||||||
* @return Pointer to the newly added fighter, or NULL if the battle is
|
|
||||||
* already full.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleAddFighter(
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the battle: enters BATTLE_STATE_OPENING and marks the battle
|
|
||||||
* active. Call once every fighter has been added via battleAddFighter.
|
|
||||||
*
|
|
||||||
* @param encounterType Determines the opening round's execution order.
|
|
||||||
* @param fleeAvailable Whether the party may attempt to flee this battle.
|
|
||||||
*/
|
*/
|
||||||
void battleStart(
|
void battleStart(
|
||||||
const battleencountertype_t encounterType,
|
battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]
|
||||||
const bool_t fleeAvailable
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the battle system.
|
||||||
|
*/
|
||||||
|
void battleUpdate(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes of the battle, clearing all fighters and marking it inactive.
|
* Disposes of the battle, clearing all fighters and marking it inactive.
|
||||||
*/
|
*/
|
||||||
void battleDispose(void);
|
void battleDispose(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fighter currently awaiting a player decision.
|
|
||||||
*
|
|
||||||
* @return Pointer to the fighter awaiting a decision, or NULL if the battle
|
|
||||||
* isn't in BATTLE_STATE_PLAYER_SELECTION or every player-controlled
|
|
||||||
* fighter has already decided.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleGetCurrentFighter(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of living fighters on a team.
|
|
||||||
*
|
|
||||||
* @param team The team to count.
|
|
||||||
* @return Count of living fighters on that team.
|
|
||||||
*/
|
|
||||||
uint8_t battleGetAliveCount(const battlefighterteam_t team);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a physical attack from attacker onto defender: damage is the
|
|
||||||
* attacker's attack stat minus the defender's defense stat (minimum 1),
|
|
||||||
* subtracted from the defender's health. The defender is marked dead
|
|
||||||
* once health reaches 0.
|
|
||||||
*
|
|
||||||
* @param attacker The attacking fighter.
|
|
||||||
* @param defender The defending fighter.
|
|
||||||
*/
|
|
||||||
void battleResolveAttack(
|
|
||||||
battlefighter_t *attacker,
|
|
||||||
battlefighter_t *defender
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the battle has been won or lost, transitioning to
|
|
||||||
* BATTLE_STATE_ENDED and updating BATTLE.result if so. Does nothing if a
|
|
||||||
* result has already been set (e.g. by a successful flee).
|
|
||||||
*
|
|
||||||
* @return The battle's current result.
|
|
||||||
*/
|
|
||||||
battleresult_t battleCheckResult(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues a move for a fighter to perform once BATTLE_STATE_MOVES_EXECUTING
|
|
||||||
* reaches them this round, overwriting any move already queued for that
|
|
||||||
* fighter.
|
|
||||||
*
|
|
||||||
* @param fighterIndex Index into BATTLE.fighters of the deciding fighter.
|
|
||||||
* @param move The move to perform.
|
|
||||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
|
||||||
*/
|
|
||||||
void battleQueueMove(
|
|
||||||
const uint8_t fighterIndex,
|
|
||||||
const battlemoveid_t move,
|
|
||||||
const uint8_t targetIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits the currently-selecting fighter's attack against a target, if the
|
|
||||||
* battle is in BATTLE_STATE_PLAYER_SELECTION and awaiting a decision.
|
|
||||||
* Queues the move and advances the selection cursor.
|
|
||||||
*
|
|
||||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
|
||||||
*/
|
|
||||||
void battlePlayerAttack(const uint8_t targetIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits a flee attempt for the currently-selecting fighter, if the battle
|
|
||||||
* is in BATTLE_STATE_PLAYER_SELECTION and fleeing is available for this
|
|
||||||
* battle. Always succeeds, ending the battle with BATTLE_RESULT_FLED.
|
|
||||||
*/
|
|
||||||
void battlePlayerFlee(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the battle simulation for one frame, dispatching on BATTLE.state.
|
|
||||||
* No-op if the battle isn't active, has already ended, or
|
|
||||||
* CUTSCENE_PAUSE_BATTLE is set.
|
|
||||||
*/
|
|
||||||
void battleUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds BATTLE.executionOrder/executionCount from every currently living
|
|
||||||
* fighter, sorted by speed descending.
|
|
||||||
*
|
|
||||||
* @param applyEncounterBias If true, reorders the freshly speed-sorted
|
|
||||||
* queue so BATTLE.encounterType's favoured team goes first (used only
|
|
||||||
* for the opening round).
|
|
||||||
*/
|
|
||||||
void battleBuildExecutionOrder(const bool_t applyEncounterBias);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stably partitions BATTLE.executionOrder so every fighter on the given team
|
|
||||||
* comes first, preserving each side's relative (speed-sorted) order.
|
|
||||||
*
|
|
||||||
* @param team The team to move to the front of the execution order.
|
|
||||||
*/
|
|
||||||
void battleMoveTeamFirst(const battlefighterteam_t team);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Picks an AI target for fighter: the lowest-health living fighter on
|
|
||||||
* the opposing team.
|
|
||||||
*
|
|
||||||
* @param fighter The AI-controlled fighter choosing a target.
|
|
||||||
* @return The chosen target, or NULL if the opposing team has no
|
|
||||||
* living fighters.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets BATTLE.state.
|
|
||||||
*
|
|
||||||
* @param next The state to transition to.
|
|
||||||
*/
|
|
||||||
void battleSetState(const battlestate_t next);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets BATTLE.result and transitions to BATTLE_STATE_ENDED.
|
|
||||||
*
|
|
||||||
* @param result The result to end the battle with.
|
|
||||||
*/
|
|
||||||
void battleSetResult(const battleresult_t result);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a fighter is a live, undecided candidate for the given
|
|
||||||
* controller -- i.e. whether PLAYER_SELECTION or AI_SELECTION should still
|
|
||||||
* be deciding a move for it this round.
|
|
||||||
*
|
|
||||||
* @param fighterIndex Index into BATTLE.fighters to check.
|
|
||||||
* @param controller The controller PLAYER_SELECTION/AI_SELECTION is
|
|
||||||
* currently deciding for.
|
|
||||||
* @return True if the fighter is alive, matches controller, and has no
|
|
||||||
* move queued yet.
|
|
||||||
*/
|
|
||||||
bool_t battleFighterNeedsDecision(
|
|
||||||
const uint8_t fighterIndex,
|
|
||||||
const battlefightercontroller_t controller
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances BATTLE.selectionIndex to the next player-controlled fighter that
|
|
||||||
* still needs a decision, or transitions to BATTLE_STATE_AI_SELECTION once
|
|
||||||
* none remain.
|
|
||||||
*/
|
|
||||||
void battleAdvanceSelection(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles BATTLE_STATE_PRE_ROUND: rebuilds the execution order and moves on
|
|
||||||
* to BATTLE_STATE_PLAYER_SELECTION, positioning the selection cursor.
|
|
||||||
*/
|
|
||||||
void battleUpdatePreRound(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles BATTLE_STATE_AI_SELECTION: queues an attack for every undecided
|
|
||||||
* AI-controlled fighter, then moves on to BATTLE_STATE_MOVES_EXECUTING.
|
|
||||||
*/
|
|
||||||
void battleUpdateAiSelection(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles BATTLE_STATE_MOVES_EXECUTING: resolves one queued move from
|
|
||||||
* BATTLE.executionOrder per call, or transitions to BATTLE_STATE_POST_ROUND
|
|
||||||
* once every fighter this round has been processed.
|
|
||||||
*/
|
|
||||||
void battleUpdateMovesExecuting(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles BATTLE_STATE_POST_ROUND: advances BATTLE.round and transitions
|
|
||||||
* back to BATTLE_STATE_PRE_ROUND.
|
|
||||||
*/
|
|
||||||
void battleUpdatePostRound(void);
|
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ void battleFighterInit(
|
|||||||
battlefighter_t *fighter,
|
battlefighter_t *fighter,
|
||||||
const battlefighterteam_t team,
|
const battlefighterteam_t team,
|
||||||
const battlefightercontroller_t controller,
|
const battlefightercontroller_t controller,
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
) {
|
||||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
||||||
@@ -31,13 +29,47 @@ void battleFighterInit(
|
|||||||
fighter->team = team;
|
fighter->team = team;
|
||||||
fighter->controller = controller;
|
fighter->controller = controller;
|
||||||
fighter->stats = stats;
|
fighter->stats = stats;
|
||||||
fighter->healthMax = healthMax;
|
fighter->stats.health = stats.healthMax;
|
||||||
fighter->health = healthMax;
|
fighter->stats.mp = stats.mpMax;
|
||||||
fighter->mpMax = mpMax;
|
|
||||||
fighter->mp = mpMax;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
||||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void battleFighterAbilityAdd(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlemoveid_t ability
|
||||||
|
) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
assertTrue(
|
||||||
|
fighter->abilityCount < BATTLE_FIGHTER_ABILITY_COUNT_MAX,
|
||||||
|
"Fighter already has the maximum number of abilities"
|
||||||
|
);
|
||||||
|
|
||||||
|
fighter->abilities[fighter->abilityCount++] = ability;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleFighterDamage(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const uint16_t amount
|
||||||
|
) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
|
||||||
|
fighter->stats.health =
|
||||||
|
amount >= fighter->stats.health ? 0 : fighter->stats.health - amount;
|
||||||
|
if(fighter->stats.health == 0) fighter->status = BATTLE_FIGHTER_STATUS_DEAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleFighterHeal(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const uint16_t amount
|
||||||
|
) {
|
||||||
|
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
||||||
|
if(!battleFighterIsAlive(fighter)) return;
|
||||||
|
|
||||||
|
const uint32_t healed = (uint32_t)fighter->stats.health + amount;
|
||||||
|
fighter->stats.health = healed > fighter->stats.healthMax
|
||||||
|
? fighter->stats.healthMax : (uint16_t)healed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "rpg/battle/fighter/battlefighterteam.h"
|
#include "rpg/battle/fighter/battlefighterteam.h"
|
||||||
#include "rpg/battle/fighter/battlefightercontroller.h"
|
#include "rpg/battle/fighter/battlefightercontroller.h"
|
||||||
#include "rpg/battle/fighter/battlefighterstats.h"
|
#include "rpg/battle/fighter/battlefighterstats.h"
|
||||||
|
#include "rpg/battle/fighter/battlefighterpos.h"
|
||||||
|
|
||||||
#define BATTLE_FIGHTER_ABILITY_COUNT_MAX 8
|
#define BATTLE_FIGHTER_ABILITY_COUNT_MAX 8
|
||||||
|
|
||||||
@@ -21,33 +22,27 @@ typedef struct battlefighter_s {
|
|||||||
battlefighterteam_t team;
|
battlefighterteam_t team;
|
||||||
battlefightercontroller_t controller;
|
battlefightercontroller_t controller;
|
||||||
|
|
||||||
uint16_t health;
|
|
||||||
uint16_t healthMax;
|
|
||||||
uint16_t mp;
|
|
||||||
uint16_t mpMax;
|
|
||||||
|
|
||||||
battlefighterstats_t stats;
|
battlefighterstats_t stats;
|
||||||
|
|
||||||
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||||
|
battlemoveid_t abilityCount;
|
||||||
} battlefighter_t;
|
} battlefighter_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a battle fighter in place, filling health/mp to their maximum
|
* Initializes a battle fighter in place, filling health/mp to the maximum
|
||||||
* values and setting its status to normal.
|
* values carried on stats and setting its status to normal.
|
||||||
*
|
*
|
||||||
* @param fighter Pointer to the fighter to initialize.
|
* @param fighter Pointer to the fighter to initialize.
|
||||||
* @param team The team the fighter belongs to.
|
* @param team The team the fighter belongs to.
|
||||||
* @param controller Who makes decisions for the fighter.
|
* @param controller Who makes decisions for the fighter.
|
||||||
* @param stats The fighter's base combat stats.
|
* @param stats The fighter's base combat stats, including healthMax/mpMax.
|
||||||
* @param healthMax The fighter's maximum health.
|
|
||||||
* @param mpMax The fighter's maximum mp.
|
|
||||||
*/
|
*/
|
||||||
void battleFighterInit(
|
void battleFighterInit(
|
||||||
battlefighter_t *fighter,
|
battlefighter_t *fighter,
|
||||||
const battlefighterteam_t team,
|
const battlefighterteam_t team,
|
||||||
const battlefightercontroller_t controller,
|
const battlefightercontroller_t controller,
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,3 +53,38 @@ void battleFighterInit(
|
|||||||
* @returns True if the fighter can act.
|
* @returns True if the fighter can act.
|
||||||
*/
|
*/
|
||||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an ability to the fighter's ability list.
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to add the ability to.
|
||||||
|
* @param ability The ability to add.
|
||||||
|
*/
|
||||||
|
void battleFighterAbilityAdd(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const battlemoveid_t ability
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtracts amount from the fighter's health (floored at 0), marking it
|
||||||
|
* dead once health reaches 0.
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to damage.
|
||||||
|
* @param amount The amount of damage to apply.
|
||||||
|
*/
|
||||||
|
void battleFighterDamage(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const uint16_t amount
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds amount to the fighter's health (capped at healthMax). No-op if the
|
||||||
|
* fighter isn't alive -- healing can't revive a dead fighter.
|
||||||
|
*
|
||||||
|
* @param fighter Pointer to the fighter to heal.
|
||||||
|
* @param amount The amount to heal.
|
||||||
|
*/
|
||||||
|
void battleFighterHeal(
|
||||||
|
battlefighter_t *fighter,
|
||||||
|
const uint16_t amount
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef uint8_t battlefighterpos_t;
|
||||||
|
|
||||||
|
#define BATTLE_FIGHTER_POS(x) ((battlefighterpos_t)(x))
|
||||||
|
|
||||||
|
// Left (Enemy side)
|
||||||
|
#define BATTLE_FIGHTER_POS_0 BATTLE_FIGHTER_POS(0)
|
||||||
|
#define BATTLE_FIGHTER_POS_1 BATTLE_FIGHTER_POS(1)
|
||||||
|
#define BATTLE_FIGHTER_POS_2 BATTLE_FIGHTER_POS(2)
|
||||||
|
#define BATTLE_FIGHTER_POS_3 BATTLE_FIGHTER_POS(3)
|
||||||
|
|
||||||
|
// Right (Ally side)
|
||||||
|
#define BATTLE_FIGHTER_POS_4 BATTLE_FIGHTER_POS(4)
|
||||||
|
#define BATTLE_FIGHTER_POS_5 BATTLE_FIGHTER_POS(5)
|
||||||
|
#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)
|
||||||
@@ -9,6 +9,11 @@
|
|||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
uint16_t health;
|
||||||
|
uint16_t mp;
|
||||||
|
|
||||||
|
uint16_t healthMax;
|
||||||
|
uint16_t mpMax;
|
||||||
uint16_t attack;
|
uint16_t attack;
|
||||||
uint16_t defense;
|
uint16_t defense;
|
||||||
uint16_t magic;
|
uint16_t magic;
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ typedef enum {
|
|||||||
BATTLE_FIGHTER_STATUS_NORMAL,
|
BATTLE_FIGHTER_STATUS_NORMAL,
|
||||||
BATTLE_FIGHTER_STATUS_DEAD,
|
BATTLE_FIGHTER_STATUS_DEAD,
|
||||||
|
|
||||||
|
// Poisoned, burned, confused, etc.
|
||||||
|
|
||||||
BATTLE_FIGHTER_STATUS_COUNT
|
BATTLE_FIGHTER_STATUS_COUNT
|
||||||
} battlefighterstatus_t;
|
} battlefighterstatus_t;
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
#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>
|
||||||
|
|
||||||
battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
|
battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
|
||||||
uint8_t BATTLE_MOVE_COUNT;
|
uint8_t BATTLE_MOVE_COUNT;
|
||||||
@@ -34,43 +34,92 @@ errorret_t battleMoveInit(void) {
|
|||||||
yyjson_obj_iter iter = yyjson_obj_iter_with(moves);
|
yyjson_obj_iter iter = yyjson_obj_iter_with(moves);
|
||||||
yyjson_val *key;
|
yyjson_val *key;
|
||||||
while((key = yyjson_obj_iter_next(&iter))) {
|
while((key = yyjson_obj_iter_next(&iter))) {
|
||||||
assertTrue(
|
if(BATTLE_MOVE_COUNT >= BATTLE_MOVE_COUNT_MAX) {
|
||||||
BATTLE_MOVE_COUNT < BATTLE_MOVE_COUNT_MAX,
|
assetUnlockEntry(entry);
|
||||||
"Too many battle moves defined."
|
errorThrow(
|
||||||
|
"Battle move: too many moves defined (max %u)",
|
||||||
|
(uint32_t)BATTLE_MOVE_COUNT_MAX
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
yyjson_val *val = yyjson_obj_iter_get_val(key);
|
|
||||||
battlemoveid_t id = BATTLE_MOVE_COUNT++;
|
battlemoveid_t id = BATTLE_MOVE_COUNT++;
|
||||||
battleability_t *move = &BATTLE_MOVES[id];
|
battleability_t *move = &BATTLE_MOVES[id];
|
||||||
|
|
||||||
stringCopy(move->name, yyjson_get_str(key), BATTLE_MOVE_NAME_MAX);
|
// Name
|
||||||
|
if(!yyjson_is_str(key)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move: keys in 'moves' must be strings");
|
||||||
|
}
|
||||||
|
const char_t *name = yyjson_get_str(key);
|
||||||
|
stringCopy(move->name, name, BATTLE_MOVE_NAME_MAX);
|
||||||
|
|
||||||
|
if(strlen(name) >= BATTLE_MOVE_NAME_MAX) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow(
|
||||||
|
"Battle move '%s': name must be less than %u characters",
|
||||||
|
name, (uint32_t)BATTLE_MOVE_NAME_MAX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must me object
|
||||||
|
yyjson_val *val = yyjson_obj_iter_get_val(key);
|
||||||
|
if(!yyjson_is_obj(val)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': value must be an object", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid targets
|
||||||
|
yyjson_val *enemies = yyjson_obj_get(val, "enemies");
|
||||||
|
if(enemies != NULL && !yyjson_is_bool(enemies)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': 'enemies' must be a boolean", name);
|
||||||
|
}
|
||||||
|
yyjson_val *allies = yyjson_obj_get(val, "allies");
|
||||||
|
if(allies != NULL && !yyjson_is_bool(allies)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': 'allies' must be a boolean", name);
|
||||||
|
}
|
||||||
|
yyjson_val *multiple = yyjson_obj_get(val, "multiple");
|
||||||
|
if(multiple != NULL && !yyjson_is_bool(multiple)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': 'multiple' must be a boolean", name);
|
||||||
|
}
|
||||||
|
yyjson_val *self = yyjson_obj_get(val, "self");
|
||||||
|
if(self != NULL && !yyjson_is_bool(self)) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': 'self' must be a boolean", name);
|
||||||
|
}
|
||||||
|
|
||||||
battletarget_t target = 0;
|
battletarget_t target = 0;
|
||||||
if(yyjson_get_bool(yyjson_obj_get(val, "enemies"))) {
|
if(yyjson_get_bool(enemies)) target |= BATTLE_TARGET_ENEMIES;
|
||||||
target |= BATTLE_TARGET_ENEMIES;
|
if(yyjson_get_bool(allies)) target |= BATTLE_TARGET_ALLIES;
|
||||||
}
|
if(yyjson_get_bool(multiple)) target |= BATTLE_TARGET_MULTIPLE;
|
||||||
if(yyjson_get_bool(yyjson_obj_get(val, "allies"))) {
|
if(yyjson_get_bool(self)) target |= BATTLE_TARGET_SELF;
|
||||||
target |= BATTLE_TARGET_ALLIES;
|
|
||||||
}
|
|
||||||
if(yyjson_get_bool(yyjson_obj_get(val, "multiple"))) {
|
|
||||||
target |= BATTLE_TARGET_MULTIPLE;
|
|
||||||
}
|
|
||||||
if(yyjson_get_bool(yyjson_obj_get(val, "self"))) {
|
|
||||||
target |= BATTLE_TARGET_SELF;
|
|
||||||
}
|
|
||||||
move->target = target;
|
move->target = target;
|
||||||
|
if(move->target == 0) {
|
||||||
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': missing target type", name);
|
||||||
|
}
|
||||||
|
|
||||||
const char_t *defaultTarget = yyjson_get_str(
|
// Default target (optional)
|
||||||
yyjson_obj_get(val, "default")
|
yyjson_val *defaultVal = yyjson_obj_get(val, "default");
|
||||||
);
|
if(defaultVal != NULL && !yyjson_is_str(defaultVal)) {
|
||||||
if(defaultTarget != NULL && stringEquals(defaultTarget, "enemy")) {
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move '%s': 'default' must be a string", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char_t *defaultTarget = yyjson_get_str(defaultVal);
|
||||||
|
if(defaultTarget == NULL) {
|
||||||
|
move->targetDefault = 0;
|
||||||
|
} else if(stringCompareInsensitive(defaultTarget, "enemy") == 0) {
|
||||||
move->targetDefault = BATTLE_TARGET_ENEMIES;
|
move->targetDefault = BATTLE_TARGET_ENEMIES;
|
||||||
} else if(defaultTarget != NULL && stringEquals(defaultTarget, "ally")) {
|
} else if(stringCompareInsensitive(defaultTarget, "ally") == 0) {
|
||||||
move->targetDefault = BATTLE_TARGET_ALLIES;
|
move->targetDefault = BATTLE_TARGET_ALLIES;
|
||||||
} else if(defaultTarget != NULL && stringEquals(defaultTarget, "self")) {
|
} else if(stringCompareInsensitive(defaultTarget, "self") == 0) {
|
||||||
move->targetDefault = BATTLE_TARGET_SELF;
|
move->targetDefault = BATTLE_TARGET_SELF;
|
||||||
} else {
|
} else {
|
||||||
move->targetDefault = 0;
|
assetUnlockEntry(entry);
|
||||||
|
errorThrow("Battle move %s has invalid default target.", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,11 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "battletarget.h"
|
#include "battletarget.h"
|
||||||
|
|
||||||
|
typedef uint8_t battlemoveid_t;
|
||||||
|
|
||||||
#define BATTLE_MOVE_NAME_MAX 16
|
#define BATTLE_MOVE_NAME_MAX 16
|
||||||
#define BATTLE_MOVE_COUNT_MAX 250
|
#define BATTLE_MOVE_COUNT_MAX 250
|
||||||
|
|
||||||
typedef uint8_t battlemoveid_t;
|
|
||||||
#define BATTLE_MOVE_ID_NULL ((battlemoveid_t)0)
|
#define BATTLE_MOVE_ID_NULL ((battlemoveid_t)0)
|
||||||
|
|
||||||
extern uint8_t BATTLE_MOVE_COUNT;
|
extern uint8_t BATTLE_MOVE_COUNT;
|
||||||
@@ -23,11 +24,6 @@ typedef struct {
|
|||||||
battletarget_t targetDefault;
|
battletarget_t targetDefault;
|
||||||
} battleability_t;
|
} battleability_t;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battlemoveid_t move;
|
|
||||||
uint8_t targetIndex;
|
|
||||||
} battlemoveaction_t;
|
|
||||||
|
|
||||||
extern battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
|
extern battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/battle/move/battlemove.h"
|
||||||
|
#include "rpg/battle/fighter/battlefighterpos.h"
|
||||||
|
|
||||||
|
typedef struct battlefighter_s battlefighter_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
battlemoveid_t move;
|
||||||
|
battlefighterpos_t targets[BATTLE_FIGHTER_POS_COUNT];
|
||||||
|
battlefighter_t *user;
|
||||||
|
} battlemoveaction_t;
|
||||||
@@ -33,9 +33,7 @@ uint8_t partyGetAvailableMember(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
battlefighter_t *partyAddMember(
|
battlefighter_t *partyAddMember(
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
) {
|
||||||
party_t *party = &SAVE.slot.party;
|
party_t *party = &SAVE.slot.party;
|
||||||
|
|
||||||
@@ -44,8 +42,7 @@ battlefighter_t *partyAddMember(
|
|||||||
|
|
||||||
battlefighter_t *member = &party->members[index];
|
battlefighter_t *member = &party->members[index];
|
||||||
battleFighterInit(
|
battleFighterInit(
|
||||||
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, stats
|
||||||
stats, healthMax, mpMax
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
|||||||
@@ -38,16 +38,12 @@ uint8_t partyGetAvailableMember(void);
|
|||||||
* player. If there is a free active order slot, the new member is placed
|
* player. If there is a free active order slot, the new member is placed
|
||||||
* into it.
|
* into it.
|
||||||
*
|
*
|
||||||
* @param stats The member's base combat stats.
|
* @param stats The member's base combat stats, including healthMax/mpMax.
|
||||||
* @param healthMax The member's maximum health.
|
|
||||||
* @param mpMax The member's maximum mp.
|
|
||||||
* @return Pointer to the newly added party member, or NULL if the party is
|
* @return Pointer to the newly added party member, or NULL if the party is
|
||||||
* already full.
|
* already full.
|
||||||
*/
|
*/
|
||||||
battlefighter_t *partyAddMember(
|
battlefighter_t *partyAddMember(
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# Sources
|
||||||
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
battlestate.c
|
||||||
|
battlestateopening.c
|
||||||
|
battlestatepreround.c
|
||||||
|
battlestateselection.c
|
||||||
|
battlestateexecuting.c
|
||||||
|
battlestatepostround.c
|
||||||
|
battlestatelost.c
|
||||||
|
battlestatewon.c
|
||||||
|
battlestatefled.c
|
||||||
|
battlestateending.c
|
||||||
|
)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestate.h"
|
||||||
|
|
||||||
|
battlestatefunctions_t BATTLE_STATE_FUNCTIONS[BATTLE_STATE_COUNT] = {
|
||||||
|
[BATTLE_STATE_NULL] = { 0 },
|
||||||
|
|
||||||
|
[BATTLE_STATE_OPENING] = {
|
||||||
|
.init = battleStateOpeningInit,
|
||||||
|
.update = battleStateOpeningUpdate,
|
||||||
|
.dispose = battleStateOpeningDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_PRE_ROUND] = {
|
||||||
|
.init = battleStatePreRoundInit,
|
||||||
|
.update = battleStatePreRoundUpdate,
|
||||||
|
.dispose = battleStatePreRoundDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_SELECTION] = {
|
||||||
|
.init = battleStateSelectionInit,
|
||||||
|
.update = battleStateSelectionUpdate,
|
||||||
|
.dispose = battleStateSelectionDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_EXECUTING] = {
|
||||||
|
.init = battleStateExecutingInit,
|
||||||
|
.update = battleStateExecutingUpdate,
|
||||||
|
.dispose = battleStateExecutingDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_POST_ROUND] = {
|
||||||
|
.init = battleStatePostRoundInit,
|
||||||
|
.update = battleStatePostRoundUpdate,
|
||||||
|
.dispose = battleStatePostRoundDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_LOST] = {
|
||||||
|
.init = battleStateLostInit,
|
||||||
|
.update = battleStateLostUpdate,
|
||||||
|
.dispose = battleStateLostDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_WON] = {
|
||||||
|
.init = battleStateWonInit,
|
||||||
|
.update = battleStateWonUpdate,
|
||||||
|
.dispose = battleStateWonDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_FLED] = {
|
||||||
|
.init = battleStateFledInit,
|
||||||
|
.update = battleStateFledUpdate,
|
||||||
|
.dispose = battleStateFledDispose
|
||||||
|
},
|
||||||
|
|
||||||
|
[BATTLE_STATE_ENDING] = {
|
||||||
|
.init = battleStateEndingInit,
|
||||||
|
.update = battleStateEndingUpdate,
|
||||||
|
.dispose = battleStateEndingDispose
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "battlestateopening.h"
|
||||||
|
#include "battlestatepreround.h"
|
||||||
|
#include "battlestateselection.h"
|
||||||
|
#include "battlestateexecuting.h"
|
||||||
|
#include "battlestatepostround.h"
|
||||||
|
#include "battlestatelost.h"
|
||||||
|
#include "battlestatewon.h"
|
||||||
|
#include "battlestatefled.h"
|
||||||
|
#include "battlestateending.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// No active battle.
|
||||||
|
BATTLE_STATE_NULL,
|
||||||
|
|
||||||
|
// Battle hasn't actually started yet, equiv to trainer intro in Pokemon.
|
||||||
|
BATTLE_STATE_OPENING,
|
||||||
|
// Pre round, anything that happens before AI or Player can act.
|
||||||
|
BATTLE_STATE_PRE_ROUND,
|
||||||
|
// Player is deciding what to do, waiting for their input
|
||||||
|
BATTLE_STATE_SELECTION,
|
||||||
|
// Currently executing the moves.
|
||||||
|
BATTLE_STATE_EXECUTING,
|
||||||
|
// Post round, anything that happens after AI or Player has acted, loops back
|
||||||
|
// after this point
|
||||||
|
BATTLE_STATE_POST_ROUND,
|
||||||
|
// Battle has ended with a player loss
|
||||||
|
BATTLE_STATE_LOST,
|
||||||
|
// Battle has ended with a player win
|
||||||
|
BATTLE_STATE_WON,
|
||||||
|
// Battle has ended with a player fleeing
|
||||||
|
BATTLE_STATE_FLED,
|
||||||
|
// Battle state is done, this is fade out or whatever.
|
||||||
|
BATTLE_STATE_ENDING,
|
||||||
|
|
||||||
|
BATTLE_STATE_COUNT
|
||||||
|
} battlestate_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
battlestateopening_t opening;
|
||||||
|
battlestatepreround_t preRound;
|
||||||
|
battlestateselection_t selection;
|
||||||
|
battlestateexecuting_t executing;
|
||||||
|
battlestatepostround_t postRound;
|
||||||
|
battlestatelost_t lost;
|
||||||
|
battlestatewon_t won;
|
||||||
|
battlestatefled_t fled;
|
||||||
|
battlestateending_t ending;
|
||||||
|
} battlestatedata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void (*init)();
|
||||||
|
void (*update)();
|
||||||
|
void (*dispose)();
|
||||||
|
} battlestatefunctions_t;
|
||||||
|
|
||||||
|
extern battlestatefunctions_t BATTLE_STATE_FUNCTIONS[BATTLE_STATE_COUNT];
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestateending.h"
|
||||||
|
|
||||||
|
void battleStateEndingInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateEndingUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateEndingDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestateending_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_ENDING.
|
||||||
|
*/
|
||||||
|
void battleStateEndingInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_ENDING for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateEndingUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_ENDING.
|
||||||
|
*/
|
||||||
|
void battleStateEndingDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestateexecuting.h"
|
||||||
|
|
||||||
|
void battleStateExecutingInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateExecutingUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateExecutingDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestateexecuting_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_EXECUTING.
|
||||||
|
*/
|
||||||
|
void battleStateExecutingInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_EXECUTING for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateExecutingUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_EXECUTING.
|
||||||
|
*/
|
||||||
|
void battleStateExecutingDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestatefled.h"
|
||||||
|
|
||||||
|
void battleStateFledInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateFledUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateFledDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestatefled_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_FLED.
|
||||||
|
*/
|
||||||
|
void battleStateFledInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_FLED for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateFledUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_FLED.
|
||||||
|
*/
|
||||||
|
void battleStateFledDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestatelost.h"
|
||||||
|
|
||||||
|
void battleStateLostInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateLostUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateLostDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestatelost_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_LOST.
|
||||||
|
*/
|
||||||
|
void battleStateLostInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_LOST for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateLostUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_LOST.
|
||||||
|
*/
|
||||||
|
void battleStateLostDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestateopening.h"
|
||||||
|
|
||||||
|
void battleStateOpeningInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateOpeningUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateOpeningDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestateopening_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_OPENING.
|
||||||
|
*/
|
||||||
|
void battleStateOpeningInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_OPENING for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateOpeningUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_OPENING.
|
||||||
|
*/
|
||||||
|
void battleStateOpeningDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestatepostround.h"
|
||||||
|
|
||||||
|
void battleStatePostRoundInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStatePostRoundUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStatePostRoundDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestatepostround_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_POST_ROUND.
|
||||||
|
*/
|
||||||
|
void battleStatePostRoundInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_POST_ROUND for one frame.
|
||||||
|
*/
|
||||||
|
void battleStatePostRoundUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_POST_ROUND.
|
||||||
|
*/
|
||||||
|
void battleStatePostRoundDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestatepreround.h"
|
||||||
|
|
||||||
|
void battleStatePreRoundInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStatePreRoundUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStatePreRoundDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestatepreround_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_PRE_ROUND.
|
||||||
|
*/
|
||||||
|
void battleStatePreRoundInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_PRE_ROUND for one frame.
|
||||||
|
*/
|
||||||
|
void battleStatePreRoundUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_PRE_ROUND.
|
||||||
|
*/
|
||||||
|
void battleStatePreRoundDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestateselection.h"
|
||||||
|
|
||||||
|
void battleStateSelectionInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateSelectionUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateSelectionDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestateselection_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_SELECTION.
|
||||||
|
*/
|
||||||
|
void battleStateSelectionInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_SELECTION for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateSelectionUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_SELECTION.
|
||||||
|
*/
|
||||||
|
void battleStateSelectionDispose(void);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "battlestatewon.h"
|
||||||
|
|
||||||
|
void battleStateWonInit(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateWonUpdate(void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void battleStateWonDispose(void) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} battlestatewon_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle enters BATTLE_STATE_WON.
|
||||||
|
*/
|
||||||
|
void battleStateWonInit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates BATTLE_STATE_WON for one frame.
|
||||||
|
*/
|
||||||
|
void battleStateWonUpdate(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the battle leaves BATTLE_STATE_WON.
|
||||||
|
*/
|
||||||
|
void battleStateWonDispose(void);
|
||||||
@@ -16,7 +16,6 @@ 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)
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
|
||||||
cutscenestartbattle.c
|
|
||||||
cutscenebattlewaitstate.c
|
|
||||||
cutscenebattleforcemove.c
|
|
||||||
cutsceneregularbattle.c
|
|
||||||
)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
|
||||||
|
|
||||||
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
|
|
||||||
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
|
|
||||||
|
|
||||||
void cutsceneBattleForceMoveStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
battleQueueMove(
|
|
||||||
item->battleForceMove.fighterIndex, BATTLE_MOVE_ID_ATTACK,
|
|
||||||
item->battleForceMove.targetIndex
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t cutsceneBattleForceMoveUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t cutsceneBattleForceMoveLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
) {
|
|
||||||
item->battleForceMove.fighterIndex =
|
|
||||||
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex"));
|
|
||||||
item->battleForceMove.targetIndex =
|
|
||||||
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "targetIndex"));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t fighterIndex;
|
|
||||||
uint8_t targetIndex;
|
|
||||||
} cutscenebattleforcemove_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a battle force-move step: immediately queues an attack for the
|
|
||||||
* given fighter against the given target, bypassing normal player/AI
|
|
||||||
* selection for that fighter this round.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
*/
|
|
||||||
void cutsceneBattleForceMoveStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a battle force-move step (always completes immediately).
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
* @returns true always.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneBattleForceMoveUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a BATTLE_FORCE_MOVE 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 cutsceneBattleForceMoveLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
);
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonbattlestate.h"
|
|
||||||
|
|
||||||
bool_t cutsceneBattleWaitStateUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
return BATTLE.state == item->battleWaitState.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t cutsceneBattleWaitStateLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
) {
|
|
||||||
battlestate_t stateValue;
|
|
||||||
if(!cutsceneJsonLookupBattleState(
|
|
||||||
yyjson_get_str(yyjson_obj_get(itemObj, "state")), &stateValue
|
|
||||||
)) {
|
|
||||||
errorThrow("Cutscene item (BATTLE_WAIT_STATE): invalid 'state'");
|
|
||||||
}
|
|
||||||
item->battleWaitState.state = stateValue;
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battlestate_t state;
|
|
||||||
} cutscenebattlewaitstate_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a battle wait-state step, completing once BATTLE.state reaches
|
|
||||||
* the watched state. Has no Start callback -- there's nothing to do until
|
|
||||||
* the state is actually reached.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
* @returns true once BATTLE.state equals the watched state.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneBattleWaitStateUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a BATTLE_WAIT_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 cutsceneBattleWaitStateLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
);
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
|
||||||
|
|
||||||
bool_t cutsceneRegularBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 regular-battle step. By the time this item runs, the preceding
|
|
||||||
* START_BATTLE item has already waited out the whole encounter, synced
|
|
||||||
* party HP/MP, and returned to the overworld scene - so this is currently
|
|
||||||
* just a completes-immediately hook point for whatever regular-battle-
|
|
||||||
* specific follow-up (loot, post-battle dialogue, etc.) gets added later.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
* @returns true always.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneRegularBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
// REGULAR_BATTLE has no fields of its own to parse yet - see
|
|
||||||
// cutsceneItemLoadNoop (cutsceneitembase.h), used directly as its .load
|
|
||||||
// callback.
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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/party.h"
|
|
||||||
#include "scene/scene.h"
|
|
||||||
#include "save/save.h"
|
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonbattleencounter.h"
|
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonbattleability.h"
|
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
void cutsceneStartBattleStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
const cutscenestartbattle_t *config = &item->startBattle;
|
|
||||||
|
|
||||||
battleInit();
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < config->playerCount; i++) {
|
|
||||||
battlefighter_t *member =
|
|
||||||
partyGetOrderMember(config->players[i]);
|
|
||||||
if(member == NULL) continue;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = battleAddFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
|
||||||
member->stats, member->healthMax, member->mpMax
|
|
||||||
);
|
|
||||||
if(fighter == NULL) continue;
|
|
||||||
|
|
||||||
fighter->health = member->health;
|
|
||||||
fighter->mp = member->mp;
|
|
||||||
fighter->status = member->status;
|
|
||||||
memoryCopy(fighter->abilities, member->abilities, sizeof(fighter->abilities));
|
|
||||||
}
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < config->enemyCount; i++) {
|
|
||||||
const cutscenestartbattleenemy_t *enemy = &config->enemies[i];
|
|
||||||
battlefighter_t *fighter = battleAddFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
|
||||||
enemy->stats, enemy->healthMax, enemy->mpMax
|
|
||||||
);
|
|
||||||
if(fighter == NULL) continue;
|
|
||||||
|
|
||||||
memoryCopy(fighter->abilities, enemy->abilities, sizeof(fighter->abilities));
|
|
||||||
}
|
|
||||||
|
|
||||||
battleStart(config->encounterType, config->fleeAvailable);
|
|
||||||
sceneSet(SCENE_TYPE_BATTLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t cutsceneStartBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
if(BATTLE.result == BATTLE_RESULT_NONE) return false;
|
|
||||||
|
|
||||||
const cutscenestartbattle_t *config = &item->startBattle;
|
|
||||||
|
|
||||||
// Sync ally HP/MP back to the persistent party roster. Relies on
|
|
||||||
// ally fighters having been added to BATTLE.fighters in the same
|
|
||||||
// order config->players iterates (see cutsceneStartBattleStart).
|
|
||||||
uint8_t allySlot = 0;
|
|
||||||
for(uint8_t i = 0; i < config->playerCount; i++) {
|
|
||||||
battlefighter_t *member =
|
|
||||||
partyGetOrderMember(config->players[i]);
|
|
||||||
if(member == NULL) continue;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[allySlot++];
|
|
||||||
member->health = fighter->health;
|
|
||||||
member->mp = fighter->mp;
|
|
||||||
member->status = fighter->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
|
||||||
battleDispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t cutsceneStartBattleLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
) {
|
|
||||||
battleencountertype_t encounterValue;
|
|
||||||
if(!cutsceneJsonLookupBattleEncounter(
|
|
||||||
yyjson_get_str(yyjson_obj_get(itemObj, "encounterType")), &encounterValue
|
|
||||||
)) {
|
|
||||||
errorThrow("Cutscene item (START_BATTLE): invalid 'encounterType'");
|
|
||||||
}
|
|
||||||
item->startBattle.encounterType = encounterValue;
|
|
||||||
|
|
||||||
item->startBattle.fleeAvailable =
|
|
||||||
cutsceneJsonGetBool(itemObj, "fleeAvailable", true);
|
|
||||||
|
|
||||||
yyjson_val *players = yyjson_obj_get(itemObj, "players");
|
|
||||||
if(players == NULL) {
|
|
||||||
item->startBattle.playerCount = PARTY_ACTIVE_SIZE_MAX;
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
|
||||||
item->startBattle.players[i] = i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
size_t playerCount = yyjson_arr_size(players);
|
|
||||||
if(!yyjson_is_arr(players) || playerCount > PARTY_ACTIVE_SIZE_MAX) {
|
|
||||||
errorThrow(
|
|
||||||
"Cutscene item (START_BATTLE): 'players' must have 0-%u entries",
|
|
||||||
(uint32_t)PARTY_ACTIVE_SIZE_MAX
|
|
||||||
);
|
|
||||||
}
|
|
||||||
item->startBattle.playerCount = (uint8_t)playerCount;
|
|
||||||
|
|
||||||
yyjson_arr_iter playerIter = yyjson_arr_iter_with(players);
|
|
||||||
yyjson_val *playerVal;
|
|
||||||
size_t p = 0;
|
|
||||||
while((playerVal = yyjson_arr_iter_next(&playerIter)) != NULL) {
|
|
||||||
int64_t index = yyjson_get_int(playerVal);
|
|
||||||
if(index < 0 || index >= PARTY_ACTIVE_SIZE_MAX) {
|
|
||||||
errorThrow(
|
|
||||||
"Cutscene item (START_BATTLE): players[%u] must be 0-%u",
|
|
||||||
(uint32_t)p, (uint32_t)(PARTY_ACTIVE_SIZE_MAX - 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
item->startBattle.players[p] = (uint8_t)index;
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies");
|
|
||||||
size_t enemyCount = yyjson_arr_size(enemies);
|
|
||||||
if(!yyjson_is_arr(enemies) || enemyCount == 0 ||
|
|
||||||
enemyCount > CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX) {
|
|
||||||
errorThrow(
|
|
||||||
"Cutscene item (START_BATTLE): 'enemies' must have 1-%u entries",
|
|
||||||
(uint32_t)CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX
|
|
||||||
);
|
|
||||||
}
|
|
||||||
item->startBattle.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) {
|
|
||||||
yyjson_val *stats = yyjson_obj_get(enemyVal, "stats");
|
|
||||||
if(!yyjson_is_obj(stats)) {
|
|
||||||
errorThrow(
|
|
||||||
"Cutscene item (START_BATTLE): enemies[%u] is missing 'stats'",
|
|
||||||
(uint32_t)e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cutscenestartbattleenemy_t *enemy = &item->startBattle.enemies[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->healthMax =
|
|
||||||
(uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "healthMax"));
|
|
||||||
enemy->mpMax =
|
|
||||||
(uint16_t)yyjson_get_int(yyjson_obj_get(enemyVal, "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 (START_BATTLE): 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) {
|
|
||||||
battlemoveid_t ability;
|
|
||||||
if(!cutsceneJsonLookupBattleAbility(
|
|
||||||
yyjson_get_str(abilityVal), &ability
|
|
||||||
)) {
|
|
||||||
errorThrow(
|
|
||||||
"Cutscene item (START_BATTLE): enemies[%u].abilities[%u] is "
|
|
||||||
"invalid",
|
|
||||||
(uint32_t)e, (uint32_t)a
|
|
||||||
);
|
|
||||||
}
|
|
||||||
enemy->abilities[a] = ability;
|
|
||||||
a++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e++;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
#include "rpg/battle/party.h"
|
|
||||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
|
||||||
|
|
||||||
#define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battlefighterstats_t stats;
|
|
||||||
uint16_t healthMax;
|
|
||||||
uint16_t mpMax;
|
|
||||||
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
|
||||||
} cutscenestartbattleenemy_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battleencountertype_t encounterType;
|
|
||||||
bool_t fleeAvailable;
|
|
||||||
uint8_t enemyCount;
|
|
||||||
cutscenestartbattleenemy_t enemies[CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX];
|
|
||||||
|
|
||||||
// Active party order slots (see party.h) to bring into the battle, in
|
|
||||||
// the order they should be added. Defaults to every active slot, 0
|
|
||||||
// through PARTY_ACTIVE_SIZE_MAX - 1 in order, when the item's JSON
|
|
||||||
// omits "players" - see cutsceneStartBattleLoad.
|
|
||||||
uint8_t players[PARTY_ACTIVE_SIZE_MAX];
|
|
||||||
uint8_t playerCount;
|
|
||||||
} cutscenestartbattle_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a battle: seeds BATTLE with the party's active order members
|
|
||||||
* and the item's configured enemies, then switches to the battle
|
|
||||||
* scene.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
*/
|
|
||||||
void cutsceneStartBattleStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for the battle to produce a result, syncs ally HP/MP back to
|
|
||||||
* the party roster, then returns to the overworld scene.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
* @returns true once the battle has ended.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneStartBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a START_BATTLE 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 cutsceneStartBattleLoad(
|
|
||||||
yyjson_val *itemObj,
|
|
||||||
cutsceneitem_t *item
|
|
||||||
);
|
|
||||||
@@ -244,33 +244,6 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
|||||||
.load = cutsceneMapAreaWaitLoad
|
.load = cutsceneMapAreaWaitLoad
|
||||||
},
|
},
|
||||||
|
|
||||||
// Battle
|
|
||||||
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
|
|
||||||
.init = cutsceneStartBattleStart,
|
|
||||||
.update = cutsceneStartBattleUpdate,
|
|
||||||
.jsonName = "START_BATTLE",
|
|
||||||
.load = cutsceneStartBattleLoad
|
|
||||||
},
|
|
||||||
|
|
||||||
[CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE] = {
|
|
||||||
.update = cutsceneBattleWaitStateUpdate,
|
|
||||||
.jsonName = "BATTLE_WAIT_STATE",
|
|
||||||
.load = cutsceneBattleWaitStateLoad
|
|
||||||
},
|
|
||||||
|
|
||||||
[CUTSCENE_ITEM_TYPE_BATTLE_FORCE_MOVE] = {
|
|
||||||
.init = cutsceneBattleForceMoveStart,
|
|
||||||
.update = cutsceneBattleForceMoveUpdate,
|
|
||||||
.jsonName = "BATTLE_FORCE_MOVE",
|
|
||||||
.load = cutsceneBattleForceMoveLoad
|
|
||||||
},
|
|
||||||
|
|
||||||
[CUTSCENE_ITEM_TYPE_REGULAR_BATTLE] = {
|
|
||||||
.update = cutsceneRegularBattleUpdate,
|
|
||||||
.jsonName = "REGULAR_BATTLE",
|
|
||||||
.load = cutsceneItemLoadNoop
|
|
||||||
},
|
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
|
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
|
||||||
.init = cutsceneSaveDeviceCheckStart,
|
.init = cutsceneSaveDeviceCheckStart,
|
||||||
|
|||||||
@@ -40,10 +40,6 @@
|
|||||||
#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/cutscenestartbattle.h"
|
|
||||||
#include "battle/cutscenebattlewaitstate.h"
|
|
||||||
#include "battle/cutscenebattleforcemove.h"
|
|
||||||
#include "battle/cutsceneregularbattle.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"
|
||||||
@@ -116,11 +112,8 @@ struct cutsceneitem_s {
|
|||||||
cutscenemapareaadd_t mapAreaAdd;
|
cutscenemapareaadd_t mapAreaAdd;
|
||||||
cutscenemaparearemove_t mapAreaRemove;
|
cutscenemaparearemove_t mapAreaRemove;
|
||||||
cutscenemapareawait_t mapAreaWait;
|
cutscenemapareawait_t mapAreaWait;
|
||||||
cutscenestartbattle_t startBattle;
|
|
||||||
cutsceneemoji_t emoji;
|
cutsceneemoji_t emoji;
|
||||||
cutsceneshake_t shake;
|
cutsceneshake_t shake;
|
||||||
cutscenebattlewaitstate_t battleWaitState;
|
|
||||||
cutscenebattleforcemove_t battleForceMove;
|
|
||||||
cutscenemodal_t modal;
|
cutscenemodal_t modal;
|
||||||
cutscenemodaloptionsmarkers_t modalOptionsMarkers;
|
cutscenemodaloptionsmarkers_t modalOptionsMarkers;
|
||||||
cutsceneprint_t print;
|
cutsceneprint_t print;
|
||||||
|
|||||||
@@ -59,12 +59,6 @@ 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_START_BATTLE,
|
|
||||||
CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE,
|
|
||||||
CUTSCENE_ITEM_TYPE_BATTLE_FORCE_MOVE,
|
|
||||||
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
|
||||||
|
|
||||||
// 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,9 +14,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
cutscenejsonemoji.c
|
cutscenejsonemoji.c
|
||||||
cutscenejsonentitytype.c
|
cutscenejsonentitytype.c
|
||||||
cutscenejsonentitydir.c
|
cutscenejsonentitydir.c
|
||||||
cutscenejsonbattleencounter.c
|
|
||||||
cutscenejsonbattleability.c
|
|
||||||
cutscenejsonbattlestate.c
|
|
||||||
cutscenejsonscenetype.c
|
cutscenejsonscenetype.c
|
||||||
cutscenejsonuiscreentype.c
|
cutscenejsonuiscreentype.c
|
||||||
cutscenejsonaudiochannel.c
|
cutscenejsonaudiochannel.c
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "cutscenejsonbattleability.h"
|
|
||||||
#include "cutscenejsonenum.h"
|
|
||||||
|
|
||||||
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup
|
|
||||||
// (see BATTLE_MOVE_COUNT).
|
|
||||||
#define BATTLE_MOVE_ID_FIRE ((battlemoveid_t)2)
|
|
||||||
|
|
||||||
static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_ABILITY_TABLE[] = {
|
|
||||||
{ "FIRE", BATTLE_MOVE_ID_FIRE },
|
|
||||||
};
|
|
||||||
#define CUTSCENE_JSON_BATTLE_ABILITY_TABLE_COUNT \
|
|
||||||
(sizeof(CUTSCENE_JSON_BATTLE_ABILITY_TABLE) / \
|
|
||||||
sizeof(cutscenejsonenumentry_t))
|
|
||||||
|
|
||||||
bool_t cutsceneJsonLookupBattleAbility(
|
|
||||||
const char_t *name,
|
|
||||||
battlemoveid_t *out
|
|
||||||
) {
|
|
||||||
int32_t value;
|
|
||||||
if(!cutsceneJsonLookupEnumInsensitive(
|
|
||||||
CUTSCENE_JSON_BATTLE_ABILITY_TABLE,
|
|
||||||
CUTSCENE_JSON_BATTLE_ABILITY_TABLE_COUNT, name, &value
|
|
||||||
)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*out = (battlemoveid_t)value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
#include "rpg/battle/move/battlemove.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up name (case-insensitively) against the battle ability table.
|
|
||||||
*
|
|
||||||
* @param name Name to search for.
|
|
||||||
* @param out Set to the matching move id on success.
|
|
||||||
* @return true if a matching entry was found, false otherwise.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneJsonLookupBattleAbility(
|
|
||||||
const char_t *name,
|
|
||||||
battlemoveid_t *out
|
|
||||||
);
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "cutscenejsonbattleencounter.h"
|
|
||||||
#include "cutscenejsonenum.h"
|
|
||||||
|
|
||||||
static const cutscenejsonenumentry_t
|
|
||||||
CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE[] = {
|
|
||||||
{ "REGULAR", BATTLE_ENCOUNTER_REGULAR },
|
|
||||||
{ "PLAYER_ADVANTAGE", BATTLE_ENCOUNTER_PLAYER_ADVANTAGE },
|
|
||||||
{ "BACK_ATTACK", BATTLE_ENCOUNTER_BACK_ATTACK },
|
|
||||||
};
|
|
||||||
#define CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE_COUNT \
|
|
||||||
(sizeof(CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE) / \
|
|
||||||
sizeof(cutscenejsonenumentry_t))
|
|
||||||
|
|
||||||
bool_t cutsceneJsonLookupBattleEncounter(
|
|
||||||
const char_t *name,
|
|
||||||
battleencountertype_t *out
|
|
||||||
) {
|
|
||||||
int32_t value;
|
|
||||||
if(!cutsceneJsonLookupEnumInsensitive(
|
|
||||||
CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE,
|
|
||||||
CUTSCENE_JSON_BATTLE_ENCOUNTER_TABLE_COUNT, name, &value
|
|
||||||
)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*out = (battleencountertype_t)value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up name (case-insensitively) against the battle encounter type
|
|
||||||
* table.
|
|
||||||
*
|
|
||||||
* @param name Name to search for.
|
|
||||||
* @param out Set to the matching encounter type on success.
|
|
||||||
* @return true if a matching entry was found, false otherwise.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneJsonLookupBattleEncounter(
|
|
||||||
const char_t *name,
|
|
||||||
battleencountertype_t *out
|
|
||||||
);
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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[] = {
|
|
||||||
{ "NONE", BATTLE_STATE_NONE },
|
|
||||||
{ "OPENING", BATTLE_STATE_OPENING },
|
|
||||||
{ "PRE_ROUND", BATTLE_STATE_PRE_ROUND },
|
|
||||||
{ "PLAYER_SELECTION", BATTLE_STATE_PLAYER_SELECTION },
|
|
||||||
{ "AI_SELECTION", BATTLE_STATE_AI_SELECTION },
|
|
||||||
{ "MOVES_EXECUTING", BATTLE_STATE_MOVES_EXECUTING },
|
|
||||||
{ "POST_ROUND", BATTLE_STATE_POST_ROUND },
|
|
||||||
{ "ENDED", BATTLE_STATE_ENDED },
|
|
||||||
};
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up name (case-insensitively) against the battle state table.
|
|
||||||
*
|
|
||||||
* @param name Name to search for.
|
|
||||||
* @param out Set to the matching battle state on success.
|
|
||||||
* @return true if a matching entry was found, false otherwise.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneJsonLookupBattleState(const char_t *name, battlestate_t *out);
|
|
||||||
@@ -34,9 +34,11 @@ void saveSlotNewGame(void) {
|
|||||||
|
|
||||||
// TEMPORARY: placeholder starting party stats - replace once there's a
|
// TEMPORARY: placeholder starting party stats - replace once there's a
|
||||||
// real starting-party/character-creation flow.
|
// real starting-party/character-creation flow.
|
||||||
const battlefighterstats_t startingStats =
|
const battlefighterstats_t startingStats = {
|
||||||
{ .attack = 10, .defense = 5, .magic = 0, .speed = 10, .luck = 0 };
|
.attack = 10, .defense = 5, .magic = 0, .speed = 10, .luck = 0,
|
||||||
partyAddMember(startingStats, 30, 10);
|
.healthMax = 30, .mpMax = 10
|
||||||
|
};
|
||||||
|
partyAddMember(startingStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ errorret_t saveSlotVer1WriteJSON(
|
|||||||
writeUInt32("status", member->status);
|
writeUInt32("status", member->status);
|
||||||
writeUInt32("team", member->team);
|
writeUInt32("team", member->team);
|
||||||
writeUInt32("controller", member->controller);
|
writeUInt32("controller", member->controller);
|
||||||
writeUInt32("health", member->health);
|
writeUInt32("health", member->stats.health);
|
||||||
writeUInt32("healthMax", member->healthMax);
|
writeUInt32("healthMax", member->stats.healthMax);
|
||||||
writeUInt32("mp", member->mp);
|
writeUInt32("mp", member->stats.mp);
|
||||||
writeUInt32("mpMax", member->mpMax);
|
writeUInt32("mpMax", member->stats.mpMax);
|
||||||
writeUInt32("statAttack", member->stats.attack);
|
writeUInt32("statAttack", member->stats.attack);
|
||||||
writeUInt32("statDefense", member->stats.defense);
|
writeUInt32("statDefense", member->stats.defense);
|
||||||
writeUInt32("statMagic", member->stats.magic);
|
writeUInt32("statMagic", member->stats.magic);
|
||||||
@@ -93,10 +93,10 @@ errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object) {
|
|||||||
readUInt32(
|
readUInt32(
|
||||||
"controller", member->controller, BATTLE_FIGHTER_CONTROLLER_PLAYER
|
"controller", member->controller, BATTLE_FIGHTER_CONTROLLER_PLAYER
|
||||||
);
|
);
|
||||||
readUInt32("health", member->health, 0);
|
readUInt32("health", member->stats.health, 0);
|
||||||
readUInt32("healthMax", member->healthMax, 0);
|
readUInt32("healthMax", member->stats.healthMax, 0);
|
||||||
readUInt32("mp", member->mp, 0);
|
readUInt32("mp", member->stats.mp, 0);
|
||||||
readUInt32("mpMax", member->mpMax, 0);
|
readUInt32("mpMax", member->stats.mpMax, 0);
|
||||||
readUInt32("statAttack", member->stats.attack, 0);
|
readUInt32("statAttack", member->stats.attack, 0);
|
||||||
readUInt32("statDefense", member->stats.defense, 0);
|
readUInt32("statDefense", member->stats.defense, 0);
|
||||||
readUInt32("statMagic", member->stats.magic, 0);
|
readUInt32("statMagic", member->stats.magic, 0);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ errorret_t sceneBattleRender(scenedata_t *sceneData) {
|
|||||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) {
|
||||||
errorChain(sceneBattleDrawFighter(i));
|
errorChain(sceneBattleDrawFighter(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,15 +94,15 @@ void sceneBattleWorldToScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
||||||
const battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
|
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||||
|
|
||||||
// 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_COUNT_MAX; i++) {
|
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]->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||||
if(BATTLE.fighters[i].team != fighter->team) continue;
|
if(BATTLE.fighters[i]->team != fighter->team) continue;
|
||||||
if(i < fighterIndex) ordinal++;
|
if(i < fighterIndex) ordinal++;
|
||||||
teamCount++;
|
teamCount++;
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) {
|
errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) {
|
||||||
const battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
|
const battlefighter_t *fighter = BATTLE.fighters[fighterIndex];
|
||||||
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk();
|
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk();
|
||||||
|
|
||||||
vec3 position;
|
vec3 position;
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ errorret_t uiBattleHudDraw(void) {
|
|||||||
|
|
||||||
scenebattle_t *battle = &SCENE.data.battle;
|
scenebattle_t *battle = &SCENE.data.battle;
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; 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->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) continue;
|
||||||
|
|
||||||
vec3 position;
|
vec3 position;
|
||||||
@@ -60,14 +60,14 @@ errorret_t uiBattleHudDraw(void) {
|
|||||||
char_t hpText[UI_BATTLE_HUD_TEXT_MAX];
|
char_t hpText[UI_BATTLE_HUD_TEXT_MAX];
|
||||||
stringFormat(
|
stringFormat(
|
||||||
hpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.hpFormat,
|
hpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.hpFormat,
|
||||||
fighter->health, fighter->healthMax
|
fighter->stats.health, fighter->stats.healthMax
|
||||||
);
|
);
|
||||||
errorChain(textDraw(screenPos[0], screenPos[1], hpText, COLOR_WHITE, NULL));
|
errorChain(textDraw(screenPos[0], screenPos[1], hpText, COLOR_WHITE, NULL));
|
||||||
|
|
||||||
char_t mpText[UI_BATTLE_HUD_TEXT_MAX];
|
char_t mpText[UI_BATTLE_HUD_TEXT_MAX];
|
||||||
stringFormat(
|
stringFormat(
|
||||||
mpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.mpFormat,
|
mpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.mpFormat,
|
||||||
fighter->mp, fighter->mpMax
|
fighter->stats.mp, fighter->stats.mpMax
|
||||||
);
|
);
|
||||||
errorChain(textDraw(
|
errorChain(textDraw(
|
||||||
screenPos[0], screenPos[1] + UI_BATTLE_HUD_LINE_HEIGHT,
|
screenPos[0], screenPos[1] + UI_BATTLE_HUD_LINE_HEIGHT,
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ void uiBattleMenuOpenTargets(void) {
|
|||||||
uiBattleMenuTargetSelected, NULL, NULL
|
uiBattleMenuTargetSelected, NULL, NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; 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->team != enemyTeam) continue;
|
if(candidate->team != enemyTeam) continue;
|
||||||
if(!battleFighterIsAlive(candidate)) continue;
|
if(!battleFighterIsAlive(candidate)) continue;
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ void uiBattleMenuOpenTargets(void) {
|
|||||||
UI_BATTLE_MENU.targetLabels[menuIndex],
|
UI_BATTLE_MENU.targetLabels[menuIndex],
|
||||||
UI_BATTLE_MENU_TARGET_LABEL_MAX - 1,
|
UI_BATTLE_MENU_TARGET_LABEL_MAX - 1,
|
||||||
UI_BATTLE_MENU.targetFormat,
|
UI_BATTLE_MENU.targetFormat,
|
||||||
candidate->id, candidate->health, candidate->healthMax
|
candidate->id, candidate->stats.health, candidate->stats.healthMax
|
||||||
);
|
);
|
||||||
UI_BATTLE_MENU.targetFighterIndex[menuIndex] = i;
|
UI_BATTLE_MENU.targetFighterIndex[menuIndex] = i;
|
||||||
MENU_BUTTON(UI_BATTLE_MENU.targetLabels[menuIndex]);
|
MENU_BUTTON(UI_BATTLE_MENU.targetLabels[menuIndex]);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#define UI_BATTLE_MENU_ACTION_INDEX_FLEE 1
|
#define UI_BATTLE_MENU_ACTION_INDEX_FLEE 1
|
||||||
#define UI_BATTLE_MENU_ACTION_LABEL_MAX 32
|
#define UI_BATTLE_MENU_ACTION_LABEL_MAX 32
|
||||||
|
|
||||||
#define UI_BATTLE_MENU_TARGET_ITEM_COUNT BATTLE_FIGHTER_COUNT_MAX
|
#define UI_BATTLE_MENU_TARGET_ITEM_COUNT BATTLE_FIGHTER_POS_COUNT
|
||||||
#define UI_BATTLE_MENU_TARGET_LABEL_MAX 32
|
#define UI_BATTLE_MENU_TARGET_LABEL_MAX 32
|
||||||
#define UI_BATTLE_MENU_TARGET_FORMAT_MAX 48
|
#define UI_BATTLE_MENU_TARGET_FORMAT_MAX 48
|
||||||
|
|
||||||
|
|||||||
@@ -7,36 +7,10 @@
|
|||||||
|
|
||||||
#include "dusktest.h"
|
#include "dusktest.h"
|
||||||
#include "rpg/battle/battle.h"
|
#include "rpg/battle/battle.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
|
||||||
|
|
||||||
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
|
// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
|
||||||
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
|
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
|
||||||
|
|
||||||
// Fighter slots as added by test_battleCutsceneForceMoveOverridesTarget:
|
|
||||||
// 0 = allyA, 1 = allyB, 2 = enemy.
|
|
||||||
static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
|
|
||||||
// Waiting BEFORE pausing is the correct order: pausing first would
|
|
||||||
// freeze BATTLE.state wherever it happened to be and it would never
|
|
||||||
// reach PRE_ROUND on its own to satisfy the wait.
|
|
||||||
{
|
|
||||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE,
|
|
||||||
.battleWaitState = { .state = BATTLE_STATE_PRE_ROUND }
|
|
||||||
},
|
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_BATTLE },
|
|
||||||
// enemy (slot 2) forced onto allyA (slot 0)
|
|
||||||
{
|
|
||||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_MOVE,
|
|
||||||
.battleForceMove = { .fighterIndex = 2, .targetIndex = 0 }
|
|
||||||
},
|
|
||||||
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_NONE }
|
|
||||||
};
|
|
||||||
static cutscene_t CUTSCENE_TEST_SCRATCH = {
|
|
||||||
.items = CUTSCENE_TEST_SCRATCH_ITEMS,
|
|
||||||
.itemCount = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t),
|
|
||||||
.itemsMax = sizeof(CUTSCENE_TEST_SCRATCH_ITEMS) / sizeof(cutsceneitem_t),
|
|
||||||
.pause = CUTSCENE_PAUSE_NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
static battlefighter_t *addFighter(
|
static battlefighter_t *addFighter(
|
||||||
const battlefighterteam_t team,
|
const battlefighterteam_t team,
|
||||||
const battlefightercontroller_t controller,
|
const battlefightercontroller_t controller,
|
||||||
@@ -110,7 +84,7 @@ static void test_battlePlayerSelectionAdvancesAndSkipsDecidedFighters(
|
|||||||
|
|
||||||
battlePlayerAttack(enemy->id);
|
battlePlayerAttack(enemy->id);
|
||||||
assert_int_equal(BATTLE.moves[ally1->id].move, BATTLE_MOVE_ID_ATTACK);
|
assert_int_equal(BATTLE.moves[ally1->id].move, BATTLE_MOVE_ID_ATTACK);
|
||||||
assert_int_equal(BATTLE.moves[ally1->id].targetIndex, enemy->id);
|
assert_int_equal(BATTLE.moves[ally1->id].targets[0], enemy->id);
|
||||||
|
|
||||||
// ally2 already had a decision queued, and enemy is AI-controlled, so
|
// ally2 already had a decision queued, and enemy is AI-controlled, so
|
||||||
// there's nothing left for PLAYER_SELECTION -- it should have moved on.
|
// there's nothing left for PLAYER_SELECTION -- it should have moved on.
|
||||||
@@ -138,7 +112,7 @@ static void test_battleAiSelectionAutoQueuesUndecidedAiFighters(void **state) {
|
|||||||
battleUpdate();// AI_SELECTION auto-decides for enemy
|
battleUpdate();// AI_SELECTION auto-decides for enemy
|
||||||
assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING);
|
assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING);
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
|
assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].targetIndex, ally->id);
|
assert_int_equal(BATTLE.moves[enemy->id].targets[0], ally->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles(
|
static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles(
|
||||||
@@ -281,81 +255,6 @@ static void test_battleFleeUnavailableIsIgnored(void **state) {
|
|||||||
assert_int_equal(BATTLE.result, BATTLE_RESULT_NONE);
|
assert_int_equal(BATTLE.result, BATTLE_RESULT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exercises CUTSCENE_BATTLE_WAIT_STATE + CUTSCENE_SET_PAUSE(BATTLE) +
|
|
||||||
// CUTSCENE_BATTLE_FORCE_MOVE together, driving the cutscene and battle
|
|
||||||
// systems side by side the way rpg.c's main loop does, to prove a
|
|
||||||
// partially-scripted round actually works end-to-end (not just that each
|
|
||||||
// item type compiles).
|
|
||||||
static void test_battleCutsceneForceMoveOverridesTarget(void **state) {
|
|
||||||
|
|
||||||
|
|
||||||
battleInit();
|
|
||||||
cutsceneSystemInit();
|
|
||||||
|
|
||||||
battlefighter_t *allyA = addFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 10, 20
|
|
||||||
);
|
|
||||||
battlefighter_t *allyB = addFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 8, 20
|
|
||||||
);
|
|
||||||
battlefighter_t *enemy = addFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 3, 0, 5, 100
|
|
||||||
);
|
|
||||||
assert_int_equal(allyA->id, 0);
|
|
||||||
assert_int_equal(allyB->id, 1);
|
|
||||||
assert_int_equal(enemy->id, 2);
|
|
||||||
|
|
||||||
battleStart(BATTLE_ENCOUNTER_REGULAR, true);
|
|
||||||
cutsceneSystemStartCutscene(&CUTSCENE_TEST_SCRATCH);
|
|
||||||
|
|
||||||
// Drive both systems together until BATTLE.state first reaches PRE_ROUND:
|
|
||||||
// cutscene sees it via CUTSCENE_BATTLE_WAIT_STATE and immediately pauses
|
|
||||||
// the battle before it can cascade on to PLAYER_SELECTION.
|
|
||||||
cutsceneSystemUpdate();// WAIT_STATE: still OPENING, not satisfied yet
|
|
||||||
battleUpdate();// OPENING -> PRE_ROUND
|
|
||||||
cutsceneSystemUpdate();// WAIT_STATE satisfied -> SET_PAUSE(BATTLE) applied
|
|
||||||
battleUpdate();// paused: no-op
|
|
||||||
|
|
||||||
assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);
|
|
||||||
assert_true(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE);
|
|
||||||
|
|
||||||
cutsceneSystemUpdate();// SET_PAUSE done -> FORCE_MOVE queues enemy's move
|
|
||||||
battleUpdate();// still paused: no-op
|
|
||||||
|
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
|
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
|
|
||||||
assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);// still frozen
|
|
||||||
|
|
||||||
cutsceneSystemUpdate();// FORCE_MOVE done -> SET_PAUSE(NONE) lifts it
|
|
||||||
battleUpdate();// unpaused: PRE_ROUND -> PLAYER_SELECTION, allyA first
|
|
||||||
|
|
||||||
assert_false(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE);
|
|
||||||
assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION);
|
|
||||||
assert_ptr_equal(battleGetCurrentFighter(), allyA);
|
|
||||||
// The forced move from three frames ago survived untouched.
|
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
|
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
|
|
||||||
|
|
||||||
battlePlayerAttack(enemy->id);// allyA -> AI_SELECTION would be next...
|
|
||||||
assert_ptr_equal(battleGetCurrentFighter(), allyB);
|
|
||||||
battlePlayerAttack(enemy->id);// ...but AI_SELECTION must skip the enemy,
|
|
||||||
// since it's decided
|
|
||||||
assert_int_equal(BATTLE.state, BATTLE_STATE_AI_SELECTION);
|
|
||||||
|
|
||||||
// AI_SELECTION: enemy already decided, skipped -> MOVES_EXECUTING
|
|
||||||
battleUpdate();
|
|
||||||
// untouched
|
|
||||||
assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
|
|
||||||
|
|
||||||
battleUpdate();// allyA attacks enemy
|
|
||||||
battleUpdate();// allyB attacks enemy
|
|
||||||
battleUpdate();// enemy attacks its forced target: allyA, not allyB
|
|
||||||
|
|
||||||
assert_int_equal(enemy->health, 90);// 100 - 5 (allyA) - 5 (allyB)
|
|
||||||
assert_int_equal(allyA->health, 17);// 20 - 3 (enemy's forced attack)
|
|
||||||
assert_int_equal(allyB->health, 20);// never targeted -- proves the override
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
const struct CMUnitTest tests[] = {
|
const struct CMUnitTest tests[] = {
|
||||||
cmocka_unit_test(test_battleStartEntersOpeningThenPreRound),
|
cmocka_unit_test(test_battleStartEntersOpeningThenPreRound),
|
||||||
@@ -366,7 +265,6 @@ int main(int argc, char** argv) {
|
|||||||
cmocka_unit_test(test_battleLossEndsInStateEnded),
|
cmocka_unit_test(test_battleLossEndsInStateEnded),
|
||||||
cmocka_unit_test(test_battleFleeEndsInStateEndedImmediately),
|
cmocka_unit_test(test_battleFleeEndsInStateEndedImmediately),
|
||||||
cmocka_unit_test(test_battleFleeUnavailableIsIgnored),
|
cmocka_unit_test(test_battleFleeUnavailableIsIgnored),
|
||||||
cmocka_unit_test(test_battleCutsceneForceMoveOverridesTarget),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
|||||||
Reference in New Issue
Block a user