Battle changes, first pass.

This commit is contained in:
2026-09-19 22:30:01 -05:00
parent c1c8174b27
commit d8273a225c
35 changed files with 618 additions and 499 deletions
+15 -5
View File
@@ -1,11 +1,21 @@
{ {
"moves": { "moves": {
"fire": { "fire": {
"ability": "FIRE", // Target flags
"power": 12, "enemies": true,
"mpCost": 4, "allies": true,
"target": "SINGLE_ENEMY", "multiple": false,
"cutscene": "cutscenes/battle/fire.jsonc" "self": true,
"default": "enemy",
"target": "SINGLE_ENEMY"
},
"slash": {
"enemies": true,
"allies": true,
"self": false,
"default": "enemy",
"target": "SINGLE_ENEMY"
} }
} }
} }
+9
View File
@@ -0,0 +1,9 @@
{
"items": [
{
"type": "MODAL",
"title": "BATTLE TEST",
"message": "BATTLE TEST"
}
]
}
@@ -0,0 +1,9 @@
{
"items": [
{
"type": "MODAL",
"title": "BATTLE TEST",
"message": "BATTLE TEST"
}
]
}
+14 -1
View File
@@ -266,6 +266,10 @@ msgstr "loading"
msgid "ui.autosave.saving" msgid "ui.autosave.saving"
msgstr "SAVING" msgstr "SAVING"
#
# Items
#
msgid "item.potion.name" msgid "item.potion.name"
msgstr "Potion" msgstr "Potion"
@@ -273,4 +277,13 @@ msgid "item.potato.name"
msgstr "Potato" msgstr "Potato"
msgid "item.apple.name" msgid "item.apple.name"
msgstr "Apple" msgstr "Apple"
#
# Moves
#
msgid "move.fire.name"
msgstr "Fire"
msgid "move.slash.name"
msgstr "Slash"
+8 -5
View File
@@ -12,13 +12,16 @@
// "DSK2" - distinct from a plain zip's "PK\x03\x04" so a stray plain zip // "DSK2" - distinct from a plain zip's "PK\x03\x04" so a stray plain zip
// never gets misread as a valid dusk.dsk. // never gets misread as a valid dusk.dsk.
#define ASSET_DSK_MAGIC_SIZE 4 #define ASSET_DSK_MAGIC_SIZE 4
#define ASSET_DSK_MAGIC "DSK2" #define ASSET_DSK_MAGIC "DSK"
#define ASSET_DSK_VERSION 2 #define ASSET_DSK_VERSION 2
// magic(4) + version(4) + size(4) + checksum(4), all little-endian // Magic + version + size + checksum
// regardless of host - see assetDskParseHeader. The zip archive itself #define ASSET_DSK_HEADER_SIZE ( \
// starts immediately after, at ASSET_DSK_HEADER_SIZE. ASSET_DSK_MAGIC_SIZE + \
#define ASSET_DSK_HEADER_SIZE 16 sizeof(uint32_t) + \
sizeof(uint32_t) + \
sizeof(uint32_t) \
)
/** /**
* Parsed dusk.dsk (DSK2 format) header. dusk.dsk is a single uncompressed * Parsed dusk.dsk (DSK2 format) header. dusk.dsk is a single uncompressed
+3 -1
View File
@@ -7,6 +7,8 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME} target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
battle.c battle.c
battlefighter.c
party.c party.c
) )
add_subdirectory(fighter)
add_subdirectory(move)
+21 -21
View File
@@ -10,10 +10,14 @@
#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++) { for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
BATTLE.fighters[i].id = i; BATTLE.fighters[i].id = i;
} }
@@ -103,18 +107,14 @@ battleresult_t battleCheckResult(void) {
return BATTLE.result; return BATTLE.result;
} }
void battleQueueAction( void battleQueueMove(
const uint8_t fighterIndex, const uint8_t fighterIndex,
const battleactiontype_t type, const battlemoveid_t move,
const uint8_t targetIndex const uint8_t targetIndex
) { ) {
battleaction_t *action = &BATTLE.actions[fighterIndex]; battlemoveaction_t *action = &BATTLE.moves[fighterIndex];
action->type = type; action->move = move;
action->targetIndex = targetIndex; action->targetIndex = targetIndex;
if(BATTLE.onActionDecided != NULL) {
BATTLE.onActionDecided(&BATTLE.fighters[fighterIndex], action);
}
} }
void battlePlayerAttack(const uint8_t targetIndex) { void battlePlayerAttack(const uint8_t targetIndex) {
@@ -123,7 +123,7 @@ void battlePlayerAttack(const uint8_t targetIndex) {
if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return; if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return;
if(!battleFighterIsAlive(&BATTLE.fighters[targetIndex])) return; if(!battleFighterIsAlive(&BATTLE.fighters[targetIndex])) return;
battleQueueAction(fighter->id, BATTLE_ACTION_ATTACK, targetIndex); battleQueueMove(fighter->id, BATTLE_MOVE_ID_ATTACK, targetIndex);
BATTLE.selectionIndex++; BATTLE.selectionIndex++;
battleAdvanceSelection(); battleAdvanceSelection();
} }
@@ -220,9 +220,11 @@ void battleMoveTeamFirst(const battlefighterteam_t team) {
} }
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) { battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
const battlefighterteam_t enemyTeam = const battlefighterteam_t enemyTeam = (
fighter->team == BATTLE_FIGHTER_TEAM_ALLY ? fighter->team == BATTLE_FIGHTER_TEAM_ALLY ?
BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY; BATTLE_FIGHTER_TEAM_ENEMY :
BATTLE_FIGHTER_TEAM_ALLY
);
battlefighter_t *weakest = NULL; battlefighter_t *weakest = NULL;
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) { for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
@@ -238,9 +240,7 @@ battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
} }
void battleSetState(const battlestate_t next) { void battleSetState(const battlestate_t next) {
const battlestate_t previous = BATTLE.state;
BATTLE.state = next; BATTLE.state = next;
if(BATTLE.onStateChanged != NULL) BATTLE.onStateChanged(previous, next);
} }
void battleSetResult(const battleresult_t result) { void battleSetResult(const battleresult_t result) {
@@ -254,7 +254,7 @@ bool_t battleFighterNeedsDecision(
) { ) {
return battleFighterIsAlive(&BATTLE.fighters[fighterIndex]) return battleFighterIsAlive(&BATTLE.fighters[fighterIndex])
&& BATTLE.fighters[fighterIndex].controller == controller && BATTLE.fighters[fighterIndex].controller == controller
&& BATTLE.actions[fighterIndex].type == BATTLE_ACTION_NONE; && BATTLE.moves[fighterIndex].move == BATTLE_MOVE_ID_NULL;
} }
void battleAdvanceSelection(void) { void battleAdvanceSelection(void) {
@@ -272,10 +272,10 @@ void battleAdvanceSelection(void) {
} }
void battleUpdatePreRound(void) { void battleUpdatePreRound(void) {
// No need to clear BATTLE.actions here: every living fighter's action is // No need to clear BATTLE.moves here: every living fighter's move is
// unconditionally reset to BATTLE_ACTION_NONE as it's processed in // unconditionally reset to BATTLE_MOVE_ID_NULL as it's processed in
// battleUpdateMovesExecuting, and round 1 starts pre-zeroed by // battleUpdateMovesExecuting, and round 1 starts pre-zeroed by
// battleInit(). Clearing it here would also wipe out any action a // battleInit(). Clearing it here would also wipe out any move a
// cutscene force-queued while parked at BATTLE_STATE_PRE_ROUND. // cutscene force-queued while parked at BATTLE_STATE_PRE_ROUND.
battleBuildExecutionOrder(BATTLE.round == 1); battleBuildExecutionOrder(BATTLE.round == 1);
@@ -295,7 +295,7 @@ void battleUpdateAiSelection(void) {
battleAIChooseTarget(&BATTLE.fighters[fighterIndex]); battleAIChooseTarget(&BATTLE.fighters[fighterIndex]);
if(target == NULL) continue; if(target == NULL) continue;
battleQueueAction(fighterIndex, BATTLE_ACTION_ATTACK, target->id); battleQueueMove(fighterIndex, BATTLE_MOVE_ID_ATTACK, target->id);
} }
BATTLE.executionIndex = 0; BATTLE.executionIndex = 0;
@@ -312,12 +312,12 @@ void battleUpdateMovesExecuting(void) {
battlefighter_t *fighter = &BATTLE.fighters[fighterIndex]; battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
if(!battleFighterIsAlive(fighter)) return; if(!battleFighterIsAlive(fighter)) return;
battleaction_t *action = &BATTLE.actions[fighterIndex]; battlemoveaction_t *action = &BATTLE.moves[fighterIndex];
if(action->type == BATTLE_ACTION_ATTACK) { if(action->move == BATTLE_MOVE_ID_ATTACK) {
battlefighter_t *target = &BATTLE.fighters[action->targetIndex]; battlefighter_t *target = &BATTLE.fighters[action->targetIndex];
if(battleFighterIsAlive(target)) battleResolveAttack(fighter, target); if(battleFighterIsAlive(target)) battleResolveAttack(fighter, target);
} }
action->type = BATTLE_ACTION_NONE; action->move = BATTLE_MOVE_ID_NULL;
battleCheckResult(); battleCheckResult();
} }
+15 -25
View File
@@ -6,8 +6,8 @@
*/ */
#pragma once #pragma once
#include "battlefighter.h" #include "rpg/battle/fighter/battlefighter.h"
#include "battleaction.h" #include "rpg/battle/move/battlemove.h"
#define BATTLE_FIGHTER_COUNT_MAX 8 #define BATTLE_FIGHTER_COUNT_MAX 8
@@ -43,15 +43,10 @@ typedef enum {
BATTLE_STATE_COUNT BATTLE_STATE_COUNT
} battlestate_t; } battlestate_t;
typedef void (*battlestatechangedcallback_t)(
const battlestate_t previous,
const battlestate_t next
);
typedef struct { typedef struct {
battlestate_t state; battlestate_t state;
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX]; battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
battleaction_t actions[BATTLE_FIGHTER_COUNT_MAX]; battlemoveaction_t moves[BATTLE_FIGHTER_COUNT_MAX];
battleencountertype_t encounterType; battleencountertype_t encounterType;
bool_t fleeAvailable; bool_t fleeAvailable;
@@ -66,9 +61,6 @@ typedef struct {
// Cursor into executionOrder used by BATTLE_STATE_PLAYER_SELECTION to find // Cursor into executionOrder used by BATTLE_STATE_PLAYER_SELECTION to find
// the next player-controlled fighter that still needs a decision. // the next player-controlled fighter that still needs a decision.
uint8_t selectionIndex; uint8_t selectionIndex;
battlestatechangedcallback_t onStateChanged;
battleactiondecidedcallback_t onActionDecided;
} battle_t; } battle_t;
extern battle_t BATTLE; extern battle_t BATTLE;
@@ -163,25 +155,24 @@ void battleResolveAttack(
battleresult_t battleCheckResult(void); battleresult_t battleCheckResult(void);
/** /**
* Queues an action for a fighter to perform once BATTLE_STATE_MOVES_EXECUTING * Queues a move for a fighter to perform once BATTLE_STATE_MOVES_EXECUTING
* reaches them this round, overwriting any action already queued for that * reaches them this round, overwriting any move already queued for that
* fighter. Fires BATTLE.onActionDecided. * fighter.
* *
* @param fighterIndex Index into BATTLE.fighters of the deciding fighter. * @param fighterIndex Index into BATTLE.fighters of the deciding fighter.
* @param type The type of action to perform. * @param move The move to perform.
* @param targetIndex Index into BATTLE.fighters of the target, meaningful * @param targetIndex Index into BATTLE.fighters of the target.
* for BATTLE_ACTION_ATTACK.
*/ */
void battleQueueAction( void battleQueueMove(
const uint8_t fighterIndex, const uint8_t fighterIndex,
const battleactiontype_t type, const battlemoveid_t move,
const uint8_t targetIndex const uint8_t targetIndex
); );
/** /**
* Submits the currently-selecting fighter's attack against a target, if the * Submits the currently-selecting fighter's attack against a target, if the
* battle is in BATTLE_STATE_PLAYER_SELECTION and awaiting a decision. * battle is in BATTLE_STATE_PLAYER_SELECTION and awaiting a decision.
* Queues the action and advances the selection cursor. * Queues the move and advances the selection cursor.
* *
* @param targetIndex Index into BATTLE.fighters of the target. * @param targetIndex Index into BATTLE.fighters of the target.
*/ */
@@ -230,8 +221,7 @@ void battleMoveTeamFirst(const battlefighterteam_t team);
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter); battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter);
/** /**
* Sets BATTLE.state and fires BATTLE.onStateChanged with the previous and * Sets BATTLE.state.
* new state.
* *
* @param next The state to transition to. * @param next The state to transition to.
*/ */
@@ -247,13 +237,13 @@ void battleSetResult(const battleresult_t result);
/** /**
* Checks whether a fighter is a live, undecided candidate for the given * Checks whether a fighter is a live, undecided candidate for the given
* controller -- i.e. whether PLAYER_SELECTION or AI_SELECTION should still * controller -- i.e. whether PLAYER_SELECTION or AI_SELECTION should still
* be deciding an action for it this round. * be deciding a move for it this round.
* *
* @param fighterIndex Index into BATTLE.fighters to check. * @param fighterIndex Index into BATTLE.fighters to check.
* @param controller The controller PLAYER_SELECTION/AI_SELECTION is * @param controller The controller PLAYER_SELECTION/AI_SELECTION is
* currently deciding for. * currently deciding for.
* @return True if the fighter is alive, matches controller, and has no * @return True if the fighter is alive, matches controller, and has no
* action queued yet. * move queued yet.
*/ */
bool_t battleFighterNeedsDecision( bool_t battleFighterNeedsDecision(
const uint8_t fighterIndex, const uint8_t fighterIndex,
@@ -280,7 +270,7 @@ void battleUpdatePreRound(void);
void battleUpdateAiSelection(void); void battleUpdateAiSelection(void);
/** /**
* Handles BATTLE_STATE_MOVES_EXECUTING: resolves one queued action from * Handles BATTLE_STATE_MOVES_EXECUTING: resolves one queued move from
* BATTLE.executionOrder per call, or transitions to BATTLE_STATE_POST_ROUND * BATTLE.executionOrder per call, or transitions to BATTLE_STATE_POST_ROUND
* once every fighter this round has been processed. * once every fighter this round has been processed.
*/ */
-27
View File
@@ -1,27 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "battlefighter.h"
typedef enum {
BATTLE_ACTION_NONE, // No action decided yet for this fighter this round.
BATTLE_ACTION_ATTACK,
BATTLE_ACTION_COUNT
} battleactiontype_t;
typedef struct {
battleactiontype_t type;
uint8_t targetIndex; // Meaningful for BATTLE_ACTION_ATTACK.
} battleaction_t;
typedef void (*battleactiondecidedcallback_t)(
const battlefighter_t *fighter,
const battleaction_t *action
);
@@ -0,0 +1,10 @@
# 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
battlefighter.c
)
@@ -7,46 +7,15 @@
#pragma once #pragma once
#include "error/error.h" #include "error/error.h"
#include "battleability.h" #include "rpg/battle/move/battlemove.h"
#include "rpg/battle/fighter/battlefighterstatus.h"
#include "rpg/battle/fighter/battlefighterteam.h"
#include "rpg/battle/fighter/battlefightercontroller.h"
#include "rpg/battle/fighter/battlefighterstats.h"
#define BATTLE_FIGHTER_ABILITY_COUNT_MAX 8 #define BATTLE_FIGHTER_ABILITY_COUNT_MAX 8
// An empty status means the slot in BATTLE.fighters is unused. typedef struct battlefighter_s {
typedef enum {
BATTLE_FIGHTER_STATUS_NULL,
BATTLE_FIGHTER_STATUS_NORMAL,
BATTLE_FIGHTER_STATUS_DEAD,
BATTLE_FIGHTER_STATUS_COUNT
} battlefighterstatus_t;
typedef enum {
BATTLE_FIGHTER_TEAM_ALLY,
BATTLE_FIGHTER_TEAM_ENEMY,
BATTLE_FIGHTER_TEAM_COUNT
} battlefighterteam_t;
typedef enum {
BATTLE_FIGHTER_CONTROLLER_PLAYER,
BATTLE_FIGHTER_CONTROLLER_AI,
BATTLE_FIGHTER_CONTROLLER_COUNT
} battlefightercontroller_t;
// Base combat stats, kept separate from the resource pools (health/mp) on
// battlefighter_t so that equipment/buffs can later modify them without
// touching current health/mp state.
typedef struct {
uint16_t attack;
uint16_t defense;
uint16_t magic;
uint16_t speed;
uint16_t luck;
} battlefighterstats_t;
typedef struct {
uint8_t id; uint8_t id;
battlefighterstatus_t status; battlefighterstatus_t status;
battlefighterteam_t team; battlefighterteam_t team;
@@ -58,7 +27,7 @@ typedef struct {
uint16_t mpMax; uint16_t mpMax;
battlefighterstats_t stats; battlefighterstats_t stats;
battleability_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX]; battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
} battlefighter_t; } battlefighter_t;
/** /**
@@ -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 "error/error.h"
typedef enum {
BATTLE_FIGHTER_CONTROLLER_NONE,
BATTLE_FIGHTER_CONTROLLER_PLAYER,
BATTLE_FIGHTER_CONTROLLER_AI,
BATTLE_FIGHTER_CONTROLLER_COUNT
} battlefightercontroller_t;
@@ -0,0 +1,17 @@
/**
* 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 {
uint16_t attack;
uint16_t defense;
uint16_t magic;
uint16_t speed;
uint16_t luck;
} battlefighterstats_t;
@@ -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 "dusk.h"
typedef enum {
BATTLE_FIGHTER_STATUS_NULL,
BATTLE_FIGHTER_STATUS_NORMAL,
BATTLE_FIGHTER_STATUS_DEAD,
BATTLE_FIGHTER_STATUS_COUNT
} battlefighterstatus_t;
@@ -6,12 +6,11 @@
*/ */
#pragma once #pragma once
#include "error/error.h" #include "dusk.h"
typedef enum { typedef enum {
BATTLE_ABILITY_NULL, BATTLE_FIGHTER_TEAM_ALLY,
BATTLE_FIGHTER_TEAM_ENEMY,
BATTLE_ABILITY_FIRE, BATTLE_FIGHTER_TEAM_COUNT
} battlefighterteam_t;
BATTLE_ABILITY_COUNT
} battleability_t;
+10
View File
@@ -0,0 +1,10 @@
# 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
battlemove.c
)
+79
View File
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "battlemove.h"
#include "asset/asset.h"
#include "asset/loader/assetloader.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/string.h"
battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
uint8_t BATTLE_MOVE_COUNT;
errorret_t battleMoveInit(void) {
memoryZero(BATTLE_MOVES, sizeof(BATTLE_MOVES));
BATTLE_MOVE_COUNT = BATTLE_MOVE_ID_NULL;
// Get moves JSON.
assetentry_t *entry = assetLock(
"battle/moves.jsonc", ASSET_LOADER_TYPE_JSON, NULL
);
errorret_t ret = assetRequireLoaded(entry);
if(errorIsNotOk(ret)) {
assetUnlockEntry(entry);
errorChain(ret);
}
yyjson_val *root = yyjson_doc_get_root(entry->data.json);
yyjson_val *moves = yyjson_obj_get(root, "moves");
yyjson_obj_iter iter = yyjson_obj_iter_with(moves);
yyjson_val *key;
while((key = yyjson_obj_iter_next(&iter))) {
assertTrue(
BATTLE_MOVE_COUNT < BATTLE_MOVE_COUNT_MAX,
"Too many battle moves defined."
);
yyjson_val *val = yyjson_obj_iter_get_val(key);
battlemoveid_t id = BATTLE_MOVE_COUNT++;
battleability_t *move = &BATTLE_MOVES[id];
stringCopy(move->name, yyjson_get_str(key), BATTLE_MOVE_NAME_MAX);
battletarget_t target = 0;
if(yyjson_get_bool(yyjson_obj_get(val, "enemies"))) {
target |= BATTLE_TARGET_ENEMIES;
}
if(yyjson_get_bool(yyjson_obj_get(val, "allies"))) {
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;
const char_t *defaultTarget = yyjson_get_str(
yyjson_obj_get(val, "default")
);
if(defaultTarget != NULL && stringEquals(defaultTarget, "enemy")) {
move->targetDefault = BATTLE_TARGET_ENEMIES;
} else if(defaultTarget != NULL && stringEquals(defaultTarget, "ally")) {
move->targetDefault = BATTLE_TARGET_ALLIES;
} else if(defaultTarget != NULL && stringEquals(defaultTarget, "self")) {
move->targetDefault = BATTLE_TARGET_SELF;
} else {
move->targetDefault = 0;
}
}
assetUnlockEntry(entry);
errorOk();
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/asset.h"
#include "battletarget.h"
#define BATTLE_MOVE_NAME_MAX 16
#define BATTLE_MOVE_COUNT_MAX 250
typedef uint8_t battlemoveid_t;
#define BATTLE_MOVE_ID_NULL ((battlemoveid_t)0)
extern uint8_t BATTLE_MOVE_COUNT;
typedef struct {
char_t name[BATTLE_MOVE_NAME_MAX];
battletarget_t target;
battletarget_t targetDefault;
} battleability_t;
typedef struct {
battlemoveid_t move;
uint8_t targetIndex;
} battlemoveaction_t;
extern battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX];
/**
* Initializes the battle move subsystem, loading assets/battle/moves.jsonc.
*
* @return Any error that occurs.
*/
errorret_t battleMoveInit(void);
+16
View File
@@ -0,0 +1,16 @@
/**
* 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 battletarget_t;
#define BATTLE_TARGET_ENEMIES (1 << 0)
#define BATTLE_TARGET_ALLIES (1 << 1)
#define BATTLE_TARGET_MULTIPLE (1 << 2)
#define BATTLE_TARGET_SELF (1 << 3)
+2 -6
View File
@@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "battlefighter.h" #include "rpg/battle/fighter/battlefighter.h"
#define PARTY_MEMBER_COUNT_MAX 4 #define PARTY_MEMBER_COUNT_MAX 4
#define PARTY_ACTIVE_SIZE_MAX 3 #define PARTY_ACTIVE_SIZE_MAX 3
@@ -14,12 +14,8 @@
typedef struct { typedef struct {
battlefighter_t members[PARTY_MEMBER_COUNT_MAX]; battlefighter_t members[PARTY_MEMBER_COUNT_MAX];
// Maps an active battle slot to the roster member filling it, or
// PARTY_ORDER_EMPTY if the slot is unfilled. Only the first
// PARTY_ACTIVE_SIZE_MAX of the PARTY_MEMBER_COUNT_MAX roster members
// can be in the active lineup at once.
uint8_t order[PARTY_ACTIVE_SIZE_MAX]; uint8_t order[PARTY_ACTIVE_SIZE_MAX];
// TODO: Whether party members are "front" or "back".
} party_t; } party_t;
/** /**
@@ -7,6 +7,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
cutscenestartbattle.c cutscenestartbattle.c
cutscenebattlewaitstate.c cutscenebattlewaitstate.c
cutscenebattleforceaction.c cutscenebattleforcemove.c
cutsceneregularbattle.c cutsceneregularbattle.c
) )
@@ -7,30 +7,33 @@
#include "rpg/cutscene/item/cutsceneitem.h" #include "rpg/cutscene/item/cutsceneitem.h"
void cutsceneBattleForceActionStart( // TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
void cutsceneBattleForceMoveStart(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
) { ) {
battleQueueAction( battleQueueMove(
item->battleForceAction.fighterIndex, BATTLE_ACTION_ATTACK, item->battleForceMove.fighterIndex, BATTLE_MOVE_ID_ATTACK,
item->battleForceAction.targetIndex item->battleForceMove.targetIndex
); );
} }
bool_t cutsceneBattleForceActionUpdate( bool_t cutsceneBattleForceMoveUpdate(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
) { ) {
return true; return true;
} }
errorret_t cutsceneBattleForceActionLoad( errorret_t cutsceneBattleForceMoveLoad(
yyjson_val *itemObj, yyjson_val *itemObj,
cutsceneitem_t *item cutsceneitem_t *item
) { ) {
item->battleForceAction.fighterIndex = item->battleForceMove.fighterIndex =
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex")); (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "fighterIndex"));
item->battleForceAction.targetIndex = item->battleForceMove.targetIndex =
(uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "targetIndex")); (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "targetIndex"));
errorOk(); errorOk();
@@ -12,41 +12,41 @@
typedef struct { typedef struct {
uint8_t fighterIndex; uint8_t fighterIndex;
uint8_t targetIndex; uint8_t targetIndex;
} cutscenebattleforceaction_t; } cutscenebattleforcemove_t;
/** /**
* Starts a battle force-action step: immediately queues an attack for the * Starts a battle force-move step: immediately queues an attack for the
* given fighter against the given target, bypassing normal player/AI * given fighter against the given target, bypassing normal player/AI
* selection for that fighter this round. * selection for that fighter this round.
* *
* @param item The cutscene item. * @param item The cutscene item.
* @param data Runtime data storage. * @param data Runtime data storage.
*/ */
void cutsceneBattleForceActionStart( void cutsceneBattleForceMoveStart(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
); );
/** /**
* Updates a battle force-action step (always completes immediately). * Updates a battle force-move step (always completes immediately).
* *
* @param item The cutscene item. * @param item The cutscene item.
* @param data Runtime data storage. * @param data Runtime data storage.
* @returns true always. * @returns true always.
*/ */
bool_t cutsceneBattleForceActionUpdate( bool_t cutsceneBattleForceMoveUpdate(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
); );
/** /**
* Parses a BATTLE_FORCE_ACTION cutscene item's JSON fields into item. * Parses a BATTLE_FORCE_MOVE cutscene item's JSON fields into item.
* *
* @param itemObj The item's JSON object. * @param itemObj The item's JSON object.
* @param item Destination item, already zeroed by the caller. * @param item Destination item, already zeroed by the caller.
* @return Error code indicating success or failure of the parse. * @return Error code indicating success or failure of the parse.
*/ */
errorret_t cutsceneBattleForceActionLoad( errorret_t cutsceneBattleForceMoveLoad(
yyjson_val *itemObj, yyjson_val *itemObj,
cutsceneitem_t *item cutsceneitem_t *item
); );
@@ -183,7 +183,7 @@ errorret_t cutsceneStartBattleLoad(
yyjson_val *abilityVal; yyjson_val *abilityVal;
size_t a = 0; size_t a = 0;
while((abilityVal = yyjson_arr_iter_next(&abilityIter)) != NULL) { while((abilityVal = yyjson_arr_iter_next(&abilityIter)) != NULL) {
battleability_t ability; battlemoveid_t ability;
if(!cutsceneJsonLookupBattleAbility( if(!cutsceneJsonLookupBattleAbility(
yyjson_get_str(abilityVal), &ability yyjson_get_str(abilityVal), &ability
)) { )) {
@@ -16,7 +16,7 @@ typedef struct {
battlefighterstats_t stats; battlefighterstats_t stats;
uint16_t healthMax; uint16_t healthMax;
uint16_t mpMax; uint16_t mpMax;
battleability_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX]; battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
} cutscenestartbattleenemy_t; } cutscenestartbattleenemy_t;
typedef struct { typedef struct {
+199 -189
View File
@@ -17,27 +17,7 @@
cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
[CUTSCENE_ITEM_TYPE_NULL] = { 0 }, [CUTSCENE_ITEM_TYPE_NULL] = { 0 },
[CUTSCENE_ITEM_TYPE_TEXT] = { // Control
.init = cutsceneTextStart,
.update = cutsceneTextUpdate,
.jsonName = "TEXT",
.load = cutsceneTextLoad
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI] = {
.init = cutsceneTextMiniStart,
.update = cutsceneTextMiniUpdate,
.jsonName = "TEXT_MINI",
.load = cutsceneTextMiniLoad
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = {
.init = cutsceneTextMiniHideStart,
.update = cutsceneTextMiniHideUpdate,
.jsonName = "TEXT_MINI_HIDE",
.load = cutsceneTextMiniHideLoad
},
[CUTSCENE_ITEM_TYPE_CALLBACK] = { [CUTSCENE_ITEM_TYPE_CALLBACK] = {
.init = cutsceneCallbackStart, .init = cutsceneCallbackStart,
.update = cutsceneCallbackUpdate .update = cutsceneCallbackUpdate
@@ -57,25 +37,11 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.load = cutsceneCutsceneLoad .load = cutsceneCutsceneLoad
}, },
[CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = { [CUTSCENE_ITEM_TYPE_INSERT] = {
.init = cutsceneEntityTeleportStart, .init = cutsceneInsertStart,
.update = cutsceneEntityTeleportUpdate, .update = cutsceneInsertUpdate,
.jsonName = "ENTITY_TELEPORT", .jsonName = "INSERT",
.load = cutsceneEntityTeleportLoad .load = cutsceneInsertLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = {
.init = cutsceneEntityWalkToStart,
.update = cutsceneEntityWalkToUpdate,
.jsonName = "ENTITY_WALK_TO",
.load = cutsceneEntityWalkToLoad
},
[CUTSCENE_ITEM_TYPE_FADE] = {
.init = cutsceneFadeStart,
.update = cutsceneFadeUpdate,
.jsonName = "FADE",
.load = cutsceneFadeLoad
}, },
[CUTSCENE_ITEM_TYPE_SET_PAUSE] = { [CUTSCENE_ITEM_TYPE_SET_PAUSE] = {
@@ -90,122 +56,6 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.update = cutsceneConcurrentUpdate .update = cutsceneConcurrentUpdate
}, },
[CUTSCENE_ITEM_TYPE_ITEM_GIVE] = {
.init = cutsceneItemGiveStart,
.update = cutsceneItemGiveUpdate,
.jsonName = "ITEM_GIVE",
.load = cutsceneItemGiveLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = {
.init = cutsceneEntityRemoveStart,
.update = cutsceneEntityRemoveUpdate,
.jsonName = "ENTITY_REMOVE",
.load = cutsceneEntityRemoveLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_ADD] = {
.init = cutsceneEntityAddStart,
.update = cutsceneEntityAddUpdate,
.jsonName = "ENTITY_ADD",
.load = cutsceneEntityAddLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_TURN] = {
.init = cutsceneEntityTurnStart,
.update = cutsceneEntityTurnUpdate,
.jsonName = "ENTITY_TURN",
.load = cutsceneEntityTurnLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = {
.init = cutsceneEntityWalkToEntityStart,
.update = cutsceneEntityWalkToEntityUpdate,
.jsonName = "ENTITY_WALK_TO_ENTITY",
.load = cutsceneEntityWalkToEntityLoad
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = {
.init = cutsceneMapAreaAddStart,
.update = cutsceneMapAreaAddUpdate
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = {
.init = cutsceneMapAreaRemoveStart,
.update = cutsceneMapAreaRemoveUpdate,
.jsonName = "MAP_AREA_REMOVE",
.load = cutsceneMapAreaRemoveLoad
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = {
.init = cutsceneMapAreaWaitStart,
.update = cutsceneMapAreaWaitUpdate,
.jsonName = "MAP_AREA_WAIT",
.load = cutsceneMapAreaWaitLoad
},
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
.init = cutsceneStartBattleStart,
.update = cutsceneStartBattleUpdate,
.jsonName = "START_BATTLE",
.load = cutsceneStartBattleLoad
},
[CUTSCENE_ITEM_TYPE_EMOJI] = {
.init = cutsceneEmojiStart,
.update = cutsceneEmojiUpdate,
.jsonName = "EMOJI",
.load = cutsceneEmojiLoad
},
[CUTSCENE_ITEM_TYPE_SHAKE] = {
.init = cutsceneShakeStart,
.update = cutsceneShakeUpdate,
.jsonName = "SHAKE",
.load = cutsceneShakeLoad
},
[CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE] = {
.update = cutsceneBattleWaitStateUpdate,
.jsonName = "BATTLE_WAIT_STATE",
.load = cutsceneBattleWaitStateLoad
},
[CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION] = {
.init = cutsceneBattleForceActionStart,
.update = cutsceneBattleForceActionUpdate,
.jsonName = "BATTLE_FORCE_ACTION",
.load = cutsceneBattleForceActionLoad
},
[CUTSCENE_ITEM_TYPE_MODAL] = {
.init = cutsceneModalStart,
.update = cutsceneModalUpdate,
.jsonName = "MODAL",
.load = cutsceneModalLoad
},
[CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS] = {
.init = cutsceneModalOptionsMarkersStart,
.update = cutsceneModalOptionsMarkersUpdate,
.jsonName = "MODAL_OPTIONS_MARKERS",
.load = cutsceneModalOptionsMarkersLoad
},
[CUTSCENE_ITEM_TYPE_MODAL_CLOSE] = {
.init = cutsceneModalCloseStart,
.update = cutsceneModalCloseUpdate,
.jsonName = "MODAL_CLOSE",
.load = cutsceneItemLoadNoop
},
[CUTSCENE_ITEM_TYPE_PRINT] = {
.init = cutscenePrintStart,
.update = cutscenePrintUpdate,
.jsonName = "PRINT",
.load = cutscenePrintLoad
},
[CUTSCENE_ITEM_TYPE_MARKER] = { [CUTSCENE_ITEM_TYPE_MARKER] = {
.init = cutsceneMarkerStart, .init = cutsceneMarkerStart,
.update = cutsceneMarkerUpdate, .update = cutsceneMarkerUpdate,
@@ -234,6 +84,194 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.load = cutsceneSceneLoad .load = cutsceneSceneLoad
}, },
[CUTSCENE_ITEM_TYPE_IDLE] = {
.update = cutsceneIdleUpdate,
.jsonName = "IDLE",
.load = cutsceneItemLoadNoop
},
// Entity
[CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = {
.init = cutsceneEntityTeleportStart,
.update = cutsceneEntityTeleportUpdate,
.jsonName = "ENTITY_TELEPORT",
.load = cutsceneEntityTeleportLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = {
.init = cutsceneEntityWalkToStart,
.update = cutsceneEntityWalkToUpdate,
.jsonName = "ENTITY_WALK_TO",
.load = cutsceneEntityWalkToLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = {
.init = cutsceneEntityWalkToEntityStart,
.update = cutsceneEntityWalkToEntityUpdate,
.jsonName = "ENTITY_WALK_TO_ENTITY",
.load = cutsceneEntityWalkToEntityLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = {
.init = cutsceneEntityRemoveStart,
.update = cutsceneEntityRemoveUpdate,
.jsonName = "ENTITY_REMOVE",
.load = cutsceneEntityRemoveLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_ADD] = {
.init = cutsceneEntityAddStart,
.update = cutsceneEntityAddUpdate,
.jsonName = "ENTITY_ADD",
.load = cutsceneEntityAddLoad
},
[CUTSCENE_ITEM_TYPE_ENTITY_TURN] = {
.init = cutsceneEntityTurnStart,
.update = cutsceneEntityTurnUpdate,
.jsonName = "ENTITY_TURN",
.load = cutsceneEntityTurnLoad
},
// UI
[CUTSCENE_ITEM_TYPE_TEXT] = {
.init = cutsceneTextStart,
.update = cutsceneTextUpdate,
.jsonName = "TEXT",
.load = cutsceneTextLoad
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI] = {
.init = cutsceneTextMiniStart,
.update = cutsceneTextMiniUpdate,
.jsonName = "TEXT_MINI",
.load = cutsceneTextMiniLoad
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = {
.init = cutsceneTextMiniHideStart,
.update = cutsceneTextMiniHideUpdate,
.jsonName = "TEXT_MINI_HIDE",
.load = cutsceneTextMiniHideLoad
},
[CUTSCENE_ITEM_TYPE_PRINT] = {
.init = cutscenePrintStart,
.update = cutscenePrintUpdate,
.jsonName = "PRINT",
.load = cutscenePrintLoad
},
[CUTSCENE_ITEM_TYPE_FADE] = {
.init = cutsceneFadeStart,
.update = cutsceneFadeUpdate,
.jsonName = "FADE",
.load = cutsceneFadeLoad
},
[CUTSCENE_ITEM_TYPE_EMOJI] = {
.init = cutsceneEmojiStart,
.update = cutsceneEmojiUpdate,
.jsonName = "EMOJI",
.load = cutsceneEmojiLoad
},
[CUTSCENE_ITEM_TYPE_SHAKE] = {
.init = cutsceneShakeStart,
.update = cutsceneShakeUpdate,
.jsonName = "SHAKE",
.load = cutsceneShakeLoad
},
[CUTSCENE_ITEM_TYPE_MODAL] = {
.init = cutsceneModalStart,
.update = cutsceneModalUpdate,
.jsonName = "MODAL",
.load = cutsceneModalLoad
},
[CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS] = {
.init = cutsceneModalOptionsMarkersStart,
.update = cutsceneModalOptionsMarkersUpdate,
.jsonName = "MODAL_OPTIONS_MARKERS",
.load = cutsceneModalOptionsMarkersLoad
},
[CUTSCENE_ITEM_TYPE_MODAL_CLOSE] = {
.init = cutsceneModalCloseStart,
.update = cutsceneModalCloseUpdate,
.jsonName = "MODAL_CLOSE",
.load = cutsceneItemLoadNoop
},
[CUTSCENE_ITEM_TYPE_KEYBOARD] = {
.init = cutsceneKeyboardStart,
.update = cutsceneKeyboardUpdate
},
[CUTSCENE_ITEM_TYPE_UI_SHOW] = {
.init = cutsceneUIShowStart,
.update = cutsceneUIShowUpdate,
.jsonName = "UI_SHOW",
.load = cutsceneUIShowLoad
},
// Item
[CUTSCENE_ITEM_TYPE_ITEM_GIVE] = {
.init = cutsceneItemGiveStart,
.update = cutsceneItemGiveUpdate,
.jsonName = "ITEM_GIVE",
.load = cutsceneItemGiveLoad
},
// Map area
[CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = {
.init = cutsceneMapAreaAddStart,
.update = cutsceneMapAreaAddUpdate
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = {
.init = cutsceneMapAreaRemoveStart,
.update = cutsceneMapAreaRemoveUpdate,
.jsonName = "MAP_AREA_REMOVE",
.load = cutsceneMapAreaRemoveLoad
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = {
.init = cutsceneMapAreaWaitStart,
.update = cutsceneMapAreaWaitUpdate,
.jsonName = "MAP_AREA_WAIT",
.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
[CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = { [CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = {
.init = cutsceneSaveDeviceCheckStart, .init = cutsceneSaveDeviceCheckStart,
.update = cutsceneSaveDeviceCheckUpdate, .update = cutsceneSaveDeviceCheckUpdate,
@@ -248,11 +286,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.load = cutsceneSaveLoadAllSlotsLoad .load = cutsceneSaveLoadAllSlotsLoad
}, },
[CUTSCENE_ITEM_TYPE_KEYBOARD] = { // Audio
.init = cutsceneKeyboardStart,
.update = cutsceneKeyboardUpdate
},
[CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = { [CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = {
.init = cutsceneAudioPlayStart, .init = cutsceneAudioPlayStart,
.update = cutsceneAudioPlayUpdate, .update = cutsceneAudioPlayUpdate,
@@ -315,32 +349,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
.load = cutsceneAudioSetLoad .load = cutsceneAudioSetLoad
}, },
[CUTSCENE_ITEM_TYPE_IDLE] = { // Story
.update = cutsceneIdleUpdate,
.jsonName = "IDLE",
.load = cutsceneItemLoadNoop
},
[CUTSCENE_ITEM_TYPE_UI_SHOW] = {
.init = cutsceneUIShowStart,
.update = cutsceneUIShowUpdate,
.jsonName = "UI_SHOW",
.load = cutsceneUIShowLoad
},
[CUTSCENE_ITEM_TYPE_REGULAR_BATTLE] = {
.update = cutsceneRegularBattleUpdate,
.jsonName = "REGULAR_BATTLE",
.load = cutsceneItemLoadNoop
},
[CUTSCENE_ITEM_TYPE_INSERT] = {
.init = cutsceneInsertStart,
.update = cutsceneInsertUpdate,
.jsonName = "INSERT",
.load = cutsceneInsertLoad
},
[CUTSCENE_ITEM_TYPE_STORY_SET_FLAG] = { [CUTSCENE_ITEM_TYPE_STORY_SET_FLAG] = {
.init = cutsceneStorySetFlagStart, .init = cutsceneStorySetFlagStart,
.update = cutsceneStorySetFlagUpdate, .update = cutsceneStorySetFlagUpdate,
@@ -506,8 +515,9 @@ void cutsceneInsertResolveAndSplice(const char_t *name) {
// nothing needs to keep the doc itself resident past the parse. // nothing needs to keep the doc itself resident past the parse.
cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX]; cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX];
cutscene_t scene = { .items = items }; cutscene_t scene = { .items = items };
errorret_t parseResult = errorret_t parseResult = cutsceneParseDoc(
cutsceneParseDoc(entry->data.json, &scene, items, CUTSCENE_INSERT_ITEMS_MAX); entry->data.json, &scene, items, CUTSCENE_INSERT_ITEMS_MAX
);
assetUnlockEntry(entry); assetUnlockEntry(entry);
if(errorIsNotOk(parseResult)) { if(errorIsNotOk(parseResult)) {
errorCatch(errorPrint(parseResult)); errorCatch(errorPrint(parseResult));
+2 -2
View File
@@ -42,7 +42,7 @@
#include "maparea/cutscenemapareawait.h" #include "maparea/cutscenemapareawait.h"
#include "battle/cutscenestartbattle.h" #include "battle/cutscenestartbattle.h"
#include "battle/cutscenebattlewaitstate.h" #include "battle/cutscenebattlewaitstate.h"
#include "battle/cutscenebattleforceaction.h" #include "battle/cutscenebattleforcemove.h"
#include "battle/cutsceneregularbattle.h" #include "battle/cutsceneregularbattle.h"
#include "save/cutscenesavedevicecheck.h" #include "save/cutscenesavedevicecheck.h"
#include "save/cutscenesaveloadallslots.h" #include "save/cutscenesaveloadallslots.h"
@@ -120,7 +120,7 @@ struct cutsceneitem_s {
cutsceneemoji_t emoji; cutsceneemoji_t emoji;
cutsceneshake_t shake; cutsceneshake_t shake;
cutscenebattlewaitstate_t battleWaitState; cutscenebattlewaitstate_t battleWaitState;
cutscenebattleforceaction_t battleForceAction; cutscenebattleforcemove_t battleForceMove;
cutscenemodal_t modal; cutscenemodal_t modal;
cutscenemodaloptionsmarkers_t modalOptionsMarkers; cutscenemodaloptionsmarkers_t modalOptionsMarkers;
cutsceneprint_t print; cutsceneprint_t print;
+45 -28
View File
@@ -16,41 +16,60 @@ typedef union cutsceneitemdata_u cutsceneitemdata_t;
typedef enum { typedef enum {
CUTSCENE_ITEM_TYPE_NULL, CUTSCENE_ITEM_TYPE_NULL,
CUTSCENE_ITEM_TYPE_TEXT, // Control
CUTSCENE_ITEM_TYPE_TEXT_MINI,
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
CUTSCENE_ITEM_TYPE_CALLBACK, CUTSCENE_ITEM_TYPE_CALLBACK,
CUTSCENE_ITEM_TYPE_WAIT, CUTSCENE_ITEM_TYPE_WAIT,
CUTSCENE_ITEM_TYPE_CUTSCENE, CUTSCENE_ITEM_TYPE_CUTSCENE,
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, CUTSCENE_ITEM_TYPE_INSERT,
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
CUTSCENE_ITEM_TYPE_FADE,
CUTSCENE_ITEM_TYPE_SET_PAUSE, CUTSCENE_ITEM_TYPE_SET_PAUSE,
CUTSCENE_ITEM_TYPE_CONCURRENT, CUTSCENE_ITEM_TYPE_CONCURRENT,
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
CUTSCENE_ITEM_TYPE_ENTITY_ADD,
CUTSCENE_ITEM_TYPE_ENTITY_TURN,
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
CUTSCENE_ITEM_TYPE_START_BATTLE,
CUTSCENE_ITEM_TYPE_EMOJI,
CUTSCENE_ITEM_TYPE_SHAKE,
CUTSCENE_ITEM_TYPE_BATTLE_WAIT_STATE,
CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION,
CUTSCENE_ITEM_TYPE_MODAL,
CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS,
CUTSCENE_ITEM_TYPE_MODAL_CLOSE,
CUTSCENE_ITEM_TYPE_PRINT,
CUTSCENE_ITEM_TYPE_MARKER, CUTSCENE_ITEM_TYPE_MARKER,
CUTSCENE_ITEM_TYPE_RESTART, CUTSCENE_ITEM_TYPE_RESTART,
CUTSCENE_ITEM_TYPE_QUIT_GAME, CUTSCENE_ITEM_TYPE_QUIT_GAME,
CUTSCENE_ITEM_TYPE_SCENE, CUTSCENE_ITEM_TYPE_SCENE,
CUTSCENE_ITEM_TYPE_IDLE,
// Entity
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT,
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY,
CUTSCENE_ITEM_TYPE_ENTITY_REMOVE,
CUTSCENE_ITEM_TYPE_ENTITY_ADD,
CUTSCENE_ITEM_TYPE_ENTITY_TURN,
// UI
CUTSCENE_ITEM_TYPE_TEXT,
CUTSCENE_ITEM_TYPE_TEXT_MINI,
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
CUTSCENE_ITEM_TYPE_PRINT,
CUTSCENE_ITEM_TYPE_FADE,
CUTSCENE_ITEM_TYPE_EMOJI,
CUTSCENE_ITEM_TYPE_SHAKE,
CUTSCENE_ITEM_TYPE_MODAL,
CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS,
CUTSCENE_ITEM_TYPE_MODAL_CLOSE,
CUTSCENE_ITEM_TYPE_KEYBOARD,
CUTSCENE_ITEM_TYPE_UI_SHOW,
// Item
CUTSCENE_ITEM_TYPE_ITEM_GIVE,
// Map area
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
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
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,
CUTSCENE_ITEM_TYPE_KEYBOARD,
// Audio
CUTSCENE_ITEM_TYPE_AUDIO_PLAY, CUTSCENE_ITEM_TYPE_AUDIO_PLAY,
CUTSCENE_ITEM_TYPE_AUDIO_STOP, CUTSCENE_ITEM_TYPE_AUDIO_STOP,
CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, CUTSCENE_ITEM_TYPE_AUDIO_PAUSE,
@@ -60,10 +79,8 @@ typedef enum {
CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN,
CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP,
CUTSCENE_ITEM_TYPE_AUDIO_SET, CUTSCENE_ITEM_TYPE_AUDIO_SET,
CUTSCENE_ITEM_TYPE_IDLE,
CUTSCENE_ITEM_TYPE_UI_SHOW, // Story
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
CUTSCENE_ITEM_TYPE_INSERT,
CUTSCENE_ITEM_TYPE_STORY_SET_FLAG, CUTSCENE_ITEM_TYPE_STORY_SET_FLAG,
CUTSCENE_ITEM_TYPE_STORY_WAIT_FLAG, CUTSCENE_ITEM_TYPE_STORY_WAIT_FLAG,
@@ -8,8 +8,12 @@
#include "cutscenejsonbattleability.h" #include "cutscenejsonbattleability.h"
#include "cutscenejsonenum.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[] = { static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_ABILITY_TABLE[] = {
{ "FIRE", BATTLE_ABILITY_FIRE }, { "FIRE", BATTLE_MOVE_ID_FIRE },
}; };
#define CUTSCENE_JSON_BATTLE_ABILITY_TABLE_COUNT \ #define CUTSCENE_JSON_BATTLE_ABILITY_TABLE_COUNT \
(sizeof(CUTSCENE_JSON_BATTLE_ABILITY_TABLE) / \ (sizeof(CUTSCENE_JSON_BATTLE_ABILITY_TABLE) / \
@@ -17,7 +21,7 @@ static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_ABILITY_TABLE[] = {
bool_t cutsceneJsonLookupBattleAbility( bool_t cutsceneJsonLookupBattleAbility(
const char_t *name, const char_t *name,
battleability_t *out battlemoveid_t *out
) { ) {
int32_t value; int32_t value;
if(!cutsceneJsonLookupEnumInsensitive( if(!cutsceneJsonLookupEnumInsensitive(
@@ -26,6 +30,6 @@ bool_t cutsceneJsonLookupBattleAbility(
)) { )) {
return false; return false;
} }
*out = (battleability_t)value; *out = (battlemoveid_t)value;
return true; return true;
} }
@@ -7,16 +7,16 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#include "rpg/battle/battleability.h" #include "rpg/battle/move/battlemove.h"
/** /**
* Looks up name (case-insensitively) against the battle ability table. * Looks up name (case-insensitively) against the battle ability table.
* *
* @param name Name to search for. * @param name Name to search for.
* @param out Set to the matching ability on success. * @param out Set to the matching move id on success.
* @return true if a matching entry was found, false otherwise. * @return true if a matching entry was found, false otherwise.
*/ */
bool_t cutsceneJsonLookupBattleAbility( bool_t cutsceneJsonLookupBattleAbility(
const char_t *name, const char_t *name,
battleability_t *out battlemoveid_t *out
); );
@@ -19,7 +19,7 @@ ENTITY_GLOBAL_CALLBACK(3) {
ENTITY_GLOBAL_CALLBACK(4) { ENTITY_GLOBAL_CALLBACK(4) {
create->entity->interact.type = ENTITY_INTERACT_CUTSCENE_NAME; create->entity->interact.type = ENTITY_INTERACT_CUTSCENE_NAME;
create->entity->interact.data.cutsceneName = "battletest"; create->entity->interact.data.cutsceneName = "cutscenes/battletest.jsonc";
} }
static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = { static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = {
+2
View File
@@ -13,6 +13,7 @@
#include "rpg/item/item.h" #include "rpg/item/item.h"
#include "rpg/item/backpack.h" #include "rpg/item/backpack.h"
#include "rpg/battle/party.h" #include "rpg/battle/party.h"
#include "rpg/battle/move/battlemove.h"
#include "save/save.h" #include "save/save.h"
#include "ui/rpg/textbox/uitextboxminilist.h" #include "ui/rpg/textbox/uitextboxminilist.h"
#include "time/time.h" #include "time/time.h"
@@ -30,6 +31,7 @@ errorret_t rpgInit(void) {
errorChain(storyInit()); errorChain(storyInit());
errorChain(itemInit()); errorChain(itemInit());
errorChain(battleMoveInit());
backpackInit(); backpackInit();
partyInit(); partyInit();
cutsceneSystemInit(); cutsceneSystemInit();
+23 -119
View File
@@ -9,7 +9,10 @@
#include "rpg/battle/battle.h" #include "rpg/battle/battle.h"
#include "rpg/cutscene/cutscenesystem.h" #include "rpg/cutscene/cutscenesystem.h"
// Fighter slots as added by test_battleCutsceneForceActionOverridesTarget: // TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup.
#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1)
// Fighter slots as added by test_battleCutsceneForceMoveOverridesTarget:
// 0 = allyA, 1 = allyB, 2 = enemy. // 0 = allyA, 1 = allyB, 2 = enemy.
static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = { static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
// Waiting BEFORE pausing is the correct order: pausing first would // Waiting BEFORE pausing is the correct order: pausing first would
@@ -22,8 +25,8 @@ static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_BATTLE }, { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_BATTLE },
// enemy (slot 2) forced onto allyA (slot 0) // enemy (slot 2) forced onto allyA (slot 0)
{ {
.type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION, .type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_MOVE,
.battleForceAction = { .fighterIndex = 2, .targetIndex = 0 } .battleForceMove = { .fighterIndex = 2, .targetIndex = 0 }
}, },
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_NONE } { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = CUTSCENE_PAUSE_NONE }
}; };
@@ -101,13 +104,13 @@ static void test_battlePlayerSelectionAdvancesAndSkipsDecidedFighters(
assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION); assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION);
assert_ptr_equal(battleGetCurrentFighter(), ally1); assert_ptr_equal(battleGetCurrentFighter(), ally1);
// A forced action (as a scripted-battle cutscene item would push) marks // A forced move (as a scripted-battle cutscene item would push) marks
// ally2 as already decided, so PLAYER_SELECTION must skip it. // ally2 as already decided, so PLAYER_SELECTION must skip it.
battleQueueAction(ally2->id, BATTLE_ACTION_ATTACK, enemy->id); battleQueueMove(ally2->id, BATTLE_MOVE_ID_ATTACK, enemy->id);
battlePlayerAttack(enemy->id); battlePlayerAttack(enemy->id);
assert_int_equal(BATTLE.actions[ally1->id].type, BATTLE_ACTION_ATTACK); assert_int_equal(BATTLE.moves[ally1->id].move, BATTLE_MOVE_ID_ATTACK);
assert_int_equal(BATTLE.actions[ally1->id].targetIndex, enemy->id); assert_int_equal(BATTLE.moves[ally1->id].targetIndex, 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.
@@ -134,8 +137,8 @@ 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.actions[enemy->id].type, BATTLE_ACTION_ATTACK); assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, ally->id); assert_int_equal(BATTLE.moves[enemy->id].targetIndex, ally->id);
} }
static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles( static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles(
@@ -278,107 +281,12 @@ static void test_battleFleeUnavailableIsIgnored(void **state) {
assert_int_equal(BATTLE.result, BATTLE_RESULT_NONE); assert_int_equal(BATTLE.result, BATTLE_RESULT_NONE);
} }
static uint8_t stateChangedCount;
static battlestate_t stateChangedLog[32][2];
static void recordStateChanged(
const battlestate_t previous,
const battlestate_t next
) {
if(stateChangedCount >= 32) return;
stateChangedLog[stateChangedCount][0] = previous;
stateChangedLog[stateChangedCount][1] = next;
stateChangedCount++;
}
static void test_battleOnStateChangedFiresAcrossFullRound(void **state) {
battleInit();
stateChangedCount = 0;
BATTLE.onStateChanged = recordStateChanged;
addFighter(
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 1, 0, 10, 100
);
battlefighter_t *enemy = addFighter(
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 1, 0, 5, 100
);
battleStart(BATTLE_ENCOUNTER_REGULAR, true);// fires NONE -> OPENING
battleUpdate();// OPENING -> PRE_ROUND
battleUpdate();// PRE_ROUND -> PLAYER_SELECTION
battlePlayerAttack(enemy->id);// PLAYER_SELECTION -> AI_SELECTION
battleUpdate();// AI_SELECTION -> MOVES_EXECUTING
battleUpdate();// MOVES_EXECUTING: ally's attack resolves (still executing)
battleUpdate();// MOVES_EXECUTING: enemy's attack resolves (still executing)
battleUpdate();// both slots processed -> POST_ROUND
battleUpdate();// POST_ROUND -> PRE_ROUND, round advances
assert_int_equal(stateChangedLog[0][0], BATTLE_STATE_NONE);
assert_int_equal(stateChangedLog[0][1], BATTLE_STATE_OPENING);
assert_int_equal(stateChangedLog[1][1], BATTLE_STATE_PRE_ROUND);
assert_int_equal(stateChangedLog[2][1], BATTLE_STATE_PLAYER_SELECTION);
assert_int_equal(stateChangedLog[3][1], BATTLE_STATE_AI_SELECTION);
assert_int_equal(stateChangedLog[4][1], BATTLE_STATE_MOVES_EXECUTING);
assert_int_equal(stateChangedLog[5][1], BATTLE_STATE_POST_ROUND);
assert_int_equal(stateChangedLog[6][1], BATTLE_STATE_PRE_ROUND);
BATTLE.onStateChanged = NULL;
}
static uint8_t actionDecidedCount;
static void recordActionDecided(
const battlefighter_t *fighter,
const battleaction_t *action
) {
actionDecidedCount++;
}
static void test_battleOnActionDecidedFiresPerFighterRegardlessOfSource(
void **state
) {
battleInit();
actionDecidedCount = 0;
BATTLE.onActionDecided = recordActionDecided;
battlefighter_t *ally1 = addFighter(
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 1, 0, 10, 100
);
battlefighter_t *ally2 = addFighter(
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 1, 0, 9, 100
);
battlefighter_t *enemy = addFighter(
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 1, 0, 5, 100
);
battleStart(BATTLE_ENCOUNTER_REGULAR, true);
battleUpdate();// OPENING -> PRE_ROUND
battleUpdate();// PRE_ROUND -> PLAYER_SELECTION
// A forced action, as CUTSCENE_BATTLE_FORCE_ACTION would push, counts too.
battleQueueAction(ally2->id, BATTLE_ACTION_ATTACK, enemy->id);
assert_int_equal(actionDecidedCount, 1);
battlePlayerAttack(enemy->id);// ally1's real decision
assert_int_equal(actionDecidedCount, 2);
assert_int_equal(BATTLE.state, BATTLE_STATE_AI_SELECTION);
battleUpdate();// AI_SELECTION decides for enemy
assert_int_equal(actionDecidedCount, 3);
BATTLE.onActionDecided = NULL;
}
// Exercises CUTSCENE_BATTLE_WAIT_STATE + CUTSCENE_SET_PAUSE(BATTLE) + // Exercises CUTSCENE_BATTLE_WAIT_STATE + CUTSCENE_SET_PAUSE(BATTLE) +
// CUTSCENE_BATTLE_FORCE_ACTION together, driving the cutscene and 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 // 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 // partially-scripted round actually works end-to-end (not just that each
// item type compiles). // item type compiles).
static void test_battleCutsceneForceActionOverridesTarget(void **state) { static void test_battleCutsceneForceMoveOverridesTarget(void **state) {
battleInit(); battleInit();
@@ -411,22 +319,22 @@ static void test_battleCutsceneForceActionOverridesTarget(void **state) {
assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND); assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);
assert_true(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE); assert_true(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE);
cutsceneSystemUpdate();// SET_PAUSE done -> FORCE_ACTION queues enemy's move cutsceneSystemUpdate();// SET_PAUSE done -> FORCE_MOVE queues enemy's move
battleUpdate();// still paused: no-op battleUpdate();// still paused: no-op
assert_int_equal(BATTLE.actions[enemy->id].type, BATTLE_ACTION_ATTACK); assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, allyA->id); assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);// still frozen assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);// still frozen
cutsceneSystemUpdate();// FORCE_ACTION done -> SET_PAUSE(NONE) lifts it cutsceneSystemUpdate();// FORCE_MOVE done -> SET_PAUSE(NONE) lifts it
battleUpdate();// unpaused: PRE_ROUND -> PLAYER_SELECTION, allyA first battleUpdate();// unpaused: PRE_ROUND -> PLAYER_SELECTION, allyA first
assert_false(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE); assert_false(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_BATTLE);
assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION); assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION);
assert_ptr_equal(battleGetCurrentFighter(), allyA); assert_ptr_equal(battleGetCurrentFighter(), allyA);
// The forced action from three frames ago survived untouched. // The forced move from three frames ago survived untouched.
assert_int_equal(BATTLE.actions[enemy->id].type, BATTLE_ACTION_ATTACK); assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, allyA->id); assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
battlePlayerAttack(enemy->id);// allyA -> AI_SELECTION would be next... battlePlayerAttack(enemy->id);// allyA -> AI_SELECTION would be next...
assert_ptr_equal(battleGetCurrentFighter(), allyB); assert_ptr_equal(battleGetCurrentFighter(), allyB);
@@ -437,7 +345,7 @@ static void test_battleCutsceneForceActionOverridesTarget(void **state) {
// AI_SELECTION: enemy already decided, skipped -> MOVES_EXECUTING // AI_SELECTION: enemy already decided, skipped -> MOVES_EXECUTING
battleUpdate(); battleUpdate();
// untouched // untouched
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, allyA->id); assert_int_equal(BATTLE.moves[enemy->id].targetIndex, allyA->id);
battleUpdate();// allyA attacks enemy battleUpdate();// allyA attacks enemy
battleUpdate();// allyB attacks enemy battleUpdate();// allyB attacks enemy
@@ -458,11 +366,7 @@ 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_battleOnStateChangedFiresAcrossFullRound), cmocka_unit_test(test_battleCutsceneForceMoveOverridesTarget),
cmocka_unit_test(
test_battleOnActionDecidedFiresPerFighterRegardlessOfSource
),
cmocka_unit_test(test_battleCutsceneForceActionOverridesTarget),
}; };
return cmocka_run_group_tests(tests, NULL, NULL); return cmocka_run_group_tests(tests, NULL, NULL);
+1 -1
View File
@@ -49,7 +49,7 @@ import zipfile
import zlib import zlib
import io import io
MAGIC = b'DSK2' MAGIC = b'DSK'
VERSION = 2 VERSION = 2
HEADER_FORMAT = '<4sIII' HEADER_FORMAT = '<4sIII'
HEADER_SIZE = struct.calcsize(HEADER_FORMAT) HEADER_SIZE = struct.calcsize(HEADER_FORMAT)