Battle changes, first pass.
This commit is contained in:
@@ -1,11 +1,21 @@
|
||||
{
|
||||
"moves": {
|
||||
"fire": {
|
||||
"ability": "FIRE",
|
||||
"power": 12,
|
||||
"mpCost": 4,
|
||||
"target": "SINGLE_ENEMY",
|
||||
"cutscene": "cutscenes/battle/fire.jsonc"
|
||||
// Target flags
|
||||
"enemies": true,
|
||||
"allies": true,
|
||||
"multiple": false,
|
||||
"self": true,
|
||||
"default": "enemy",
|
||||
"target": "SINGLE_ENEMY"
|
||||
},
|
||||
|
||||
"slash": {
|
||||
"enemies": true,
|
||||
"allies": true,
|
||||
"self": false,
|
||||
"default": "enemy",
|
||||
"target": "SINGLE_ENEMY"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -266,6 +266,10 @@ msgstr "loading"
|
||||
msgid "ui.autosave.saving"
|
||||
msgstr "SAVING"
|
||||
|
||||
|
||||
#
|
||||
# Items
|
||||
#
|
||||
msgid "item.potion.name"
|
||||
msgstr "Potion"
|
||||
|
||||
@@ -273,4 +277,13 @@ msgid "item.potato.name"
|
||||
msgstr "Potato"
|
||||
|
||||
msgid "item.apple.name"
|
||||
msgstr "Apple"
|
||||
msgstr "Apple"
|
||||
|
||||
#
|
||||
# Moves
|
||||
#
|
||||
msgid "move.fire.name"
|
||||
msgstr "Fire"
|
||||
|
||||
msgid "move.slash.name"
|
||||
msgstr "Slash"
|
||||
@@ -12,13 +12,16 @@
|
||||
// "DSK2" - distinct from a plain zip's "PK\x03\x04" so a stray plain zip
|
||||
// never gets misread as a valid dusk.dsk.
|
||||
#define ASSET_DSK_MAGIC_SIZE 4
|
||||
#define ASSET_DSK_MAGIC "DSK2"
|
||||
#define ASSET_DSK_MAGIC "DSK"
|
||||
#define ASSET_DSK_VERSION 2
|
||||
|
||||
// magic(4) + version(4) + size(4) + checksum(4), all little-endian
|
||||
// regardless of host - see assetDskParseHeader. The zip archive itself
|
||||
// starts immediately after, at ASSET_DSK_HEADER_SIZE.
|
||||
#define ASSET_DSK_HEADER_SIZE 16
|
||||
// Magic + version + size + checksum
|
||||
#define ASSET_DSK_HEADER_SIZE ( \
|
||||
ASSET_DSK_MAGIC_SIZE + \
|
||||
sizeof(uint32_t) + \
|
||||
sizeof(uint32_t) + \
|
||||
sizeof(uint32_t) \
|
||||
)
|
||||
|
||||
/**
|
||||
* Parsed dusk.dsk (DSK2 format) header. dusk.dsk is a single uncompressed
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
battle.c
|
||||
battlefighter.c
|
||||
party.c
|
||||
)
|
||||
|
||||
add_subdirectory(fighter)
|
||||
add_subdirectory(move)
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
#include "util/memory.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;
|
||||
|
||||
void battleInit(void) {
|
||||
memoryZero(&BATTLE, sizeof(battle_t));
|
||||
|
||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
||||
BATTLE.fighters[i].id = i;
|
||||
}
|
||||
@@ -103,18 +107,14 @@ battleresult_t battleCheckResult(void) {
|
||||
return BATTLE.result;
|
||||
}
|
||||
|
||||
void battleQueueAction(
|
||||
void battleQueueMove(
|
||||
const uint8_t fighterIndex,
|
||||
const battleactiontype_t type,
|
||||
const battlemoveid_t move,
|
||||
const uint8_t targetIndex
|
||||
) {
|
||||
battleaction_t *action = &BATTLE.actions[fighterIndex];
|
||||
action->type = type;
|
||||
battlemoveaction_t *action = &BATTLE.moves[fighterIndex];
|
||||
action->move = move;
|
||||
action->targetIndex = targetIndex;
|
||||
|
||||
if(BATTLE.onActionDecided != NULL) {
|
||||
BATTLE.onActionDecided(&BATTLE.fighters[fighterIndex], action);
|
||||
}
|
||||
}
|
||||
|
||||
void battlePlayerAttack(const uint8_t targetIndex) {
|
||||
@@ -123,7 +123,7 @@ void battlePlayerAttack(const uint8_t targetIndex) {
|
||||
if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return;
|
||||
if(!battleFighterIsAlive(&BATTLE.fighters[targetIndex])) return;
|
||||
|
||||
battleQueueAction(fighter->id, BATTLE_ACTION_ATTACK, targetIndex);
|
||||
battleQueueMove(fighter->id, BATTLE_MOVE_ID_ATTACK, targetIndex);
|
||||
BATTLE.selectionIndex++;
|
||||
battleAdvanceSelection();
|
||||
}
|
||||
@@ -220,9 +220,11 @@ void battleMoveTeamFirst(const battlefighterteam_t team) {
|
||||
}
|
||||
|
||||
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
|
||||
const battlefighterteam_t enemyTeam =
|
||||
const battlefighterteam_t enemyTeam = (
|
||||
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;
|
||||
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) {
|
||||
const battlestate_t previous = BATTLE.state;
|
||||
BATTLE.state = next;
|
||||
if(BATTLE.onStateChanged != NULL) BATTLE.onStateChanged(previous, next);
|
||||
}
|
||||
|
||||
void battleSetResult(const battleresult_t result) {
|
||||
@@ -254,7 +254,7 @@ bool_t battleFighterNeedsDecision(
|
||||
) {
|
||||
return battleFighterIsAlive(&BATTLE.fighters[fighterIndex])
|
||||
&& BATTLE.fighters[fighterIndex].controller == controller
|
||||
&& BATTLE.actions[fighterIndex].type == BATTLE_ACTION_NONE;
|
||||
&& BATTLE.moves[fighterIndex].move == BATTLE_MOVE_ID_NULL;
|
||||
}
|
||||
|
||||
void battleAdvanceSelection(void) {
|
||||
@@ -272,10 +272,10 @@ void battleAdvanceSelection(void) {
|
||||
}
|
||||
|
||||
void battleUpdatePreRound(void) {
|
||||
// No need to clear BATTLE.actions here: every living fighter's action is
|
||||
// unconditionally reset to BATTLE_ACTION_NONE as it's processed in
|
||||
// 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 action a
|
||||
// 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);
|
||||
|
||||
@@ -295,7 +295,7 @@ void battleUpdateAiSelection(void) {
|
||||
battleAIChooseTarget(&BATTLE.fighters[fighterIndex]);
|
||||
if(target == NULL) continue;
|
||||
|
||||
battleQueueAction(fighterIndex, BATTLE_ACTION_ATTACK, target->id);
|
||||
battleQueueMove(fighterIndex, BATTLE_MOVE_ID_ATTACK, target->id);
|
||||
}
|
||||
|
||||
BATTLE.executionIndex = 0;
|
||||
@@ -312,12 +312,12 @@ void battleUpdateMovesExecuting(void) {
|
||||
battlefighter_t *fighter = &BATTLE.fighters[fighterIndex];
|
||||
if(!battleFighterIsAlive(fighter)) return;
|
||||
|
||||
battleaction_t *action = &BATTLE.actions[fighterIndex];
|
||||
if(action->type == BATTLE_ACTION_ATTACK) {
|
||||
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->type = BATTLE_ACTION_NONE;
|
||||
action->move = BATTLE_MOVE_ID_NULL;
|
||||
|
||||
battleCheckResult();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "battlefighter.h"
|
||||
#include "battleaction.h"
|
||||
#include "rpg/battle/fighter/battlefighter.h"
|
||||
#include "rpg/battle/move/battlemove.h"
|
||||
|
||||
#define BATTLE_FIGHTER_COUNT_MAX 8
|
||||
|
||||
@@ -43,15 +43,10 @@ typedef enum {
|
||||
BATTLE_STATE_COUNT
|
||||
} battlestate_t;
|
||||
|
||||
typedef void (*battlestatechangedcallback_t)(
|
||||
const battlestate_t previous,
|
||||
const battlestate_t next
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
battlestate_t state;
|
||||
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
||||
battleaction_t actions[BATTLE_FIGHTER_COUNT_MAX];
|
||||
battlemoveaction_t moves[BATTLE_FIGHTER_COUNT_MAX];
|
||||
|
||||
battleencountertype_t encounterType;
|
||||
bool_t fleeAvailable;
|
||||
@@ -66,9 +61,6 @@ typedef struct {
|
||||
// Cursor into executionOrder used by BATTLE_STATE_PLAYER_SELECTION to find
|
||||
// the next player-controlled fighter that still needs a decision.
|
||||
uint8_t selectionIndex;
|
||||
|
||||
battlestatechangedcallback_t onStateChanged;
|
||||
battleactiondecidedcallback_t onActionDecided;
|
||||
} battle_t;
|
||||
|
||||
extern battle_t BATTLE;
|
||||
@@ -163,25 +155,24 @@ void battleResolveAttack(
|
||||
battleresult_t battleCheckResult(void);
|
||||
|
||||
/**
|
||||
* Queues an action for a fighter to perform once BATTLE_STATE_MOVES_EXECUTING
|
||||
* reaches them this round, overwriting any action already queued for that
|
||||
* fighter. Fires BATTLE.onActionDecided.
|
||||
* 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 type The type of action to perform.
|
||||
* @param targetIndex Index into BATTLE.fighters of the target, meaningful
|
||||
* for BATTLE_ACTION_ATTACK.
|
||||
* @param move The move to perform.
|
||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
||||
*/
|
||||
void battleQueueAction(
|
||||
void battleQueueMove(
|
||||
const uint8_t fighterIndex,
|
||||
const battleactiontype_t type,
|
||||
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 action and advances the selection cursor.
|
||||
* Queues the move and advances the selection cursor.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Sets BATTLE.state and fires BATTLE.onStateChanged with the previous and
|
||||
* new state.
|
||||
* Sets BATTLE.state.
|
||||
*
|
||||
* @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
|
||||
* 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 controller The controller PLAYER_SELECTION/AI_SELECTION is
|
||||
* currently deciding for.
|
||||
* @return True if the fighter is alive, matches controller, and has no
|
||||
* action queued yet.
|
||||
* move queued yet.
|
||||
*/
|
||||
bool_t battleFighterNeedsDecision(
|
||||
const uint8_t fighterIndex,
|
||||
@@ -280,7 +270,7 @@ void battleUpdatePreRound(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
|
||||
* once every fighter this round has been processed.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
#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
|
||||
|
||||
// An empty status means the slot in BATTLE.fighters is unused.
|
||||
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 {
|
||||
typedef struct battlefighter_s {
|
||||
uint8_t id;
|
||||
battlefighterstatus_t status;
|
||||
battlefighterteam_t team;
|
||||
@@ -58,7 +27,7 @@ typedef struct {
|
||||
uint16_t mpMax;
|
||||
|
||||
battlefighterstats_t stats;
|
||||
battleability_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||
} 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;
|
||||
+5
-6
@@ -6,12 +6,11 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
BATTLE_ABILITY_NULL,
|
||||
BATTLE_FIGHTER_TEAM_ALLY,
|
||||
BATTLE_FIGHTER_TEAM_ENEMY,
|
||||
|
||||
BATTLE_ABILITY_FIRE,
|
||||
|
||||
BATTLE_ABILITY_COUNT
|
||||
} battleability_t;
|
||||
BATTLE_FIGHTER_TEAM_COUNT
|
||||
} battlefighterteam_t;
|
||||
@@ -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
|
||||
)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "battlefighter.h"
|
||||
#include "rpg/battle/fighter/battlefighter.h"
|
||||
|
||||
#define PARTY_MEMBER_COUNT_MAX 4
|
||||
#define PARTY_ACTIVE_SIZE_MAX 3
|
||||
@@ -14,12 +14,8 @@
|
||||
|
||||
typedef struct {
|
||||
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];
|
||||
// TODO: Whether party members are "front" or "back".
|
||||
} party_t;
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenestartbattle.c
|
||||
cutscenebattlewaitstate.c
|
||||
cutscenebattleforceaction.c
|
||||
cutscenebattleforcemove.c
|
||||
cutsceneregularbattle.c
|
||||
)
|
||||
|
||||
+11
-8
@@ -7,30 +7,33 @@
|
||||
|
||||
#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,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
battleQueueAction(
|
||||
item->battleForceAction.fighterIndex, BATTLE_ACTION_ATTACK,
|
||||
item->battleForceAction.targetIndex
|
||||
battleQueueMove(
|
||||
item->battleForceMove.fighterIndex, BATTLE_MOVE_ID_ATTACK,
|
||||
item->battleForceMove.targetIndex
|
||||
);
|
||||
}
|
||||
|
||||
bool_t cutsceneBattleForceActionUpdate(
|
||||
bool_t cutsceneBattleForceMoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
cutsceneitemdata_t *data
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
errorret_t cutsceneBattleForceActionLoad(
|
||||
errorret_t cutsceneBattleForceMoveLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
) {
|
||||
item->battleForceAction.fighterIndex =
|
||||
item->battleForceMove.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"));
|
||||
|
||||
errorOk();
|
||||
+7
-7
@@ -12,41 +12,41 @@
|
||||
typedef struct {
|
||||
uint8_t fighterIndex;
|
||||
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
|
||||
* selection for that fighter this round.
|
||||
*
|
||||
* @param item The cutscene item.
|
||||
* @param data Runtime data storage.
|
||||
*/
|
||||
void cutsceneBattleForceActionStart(
|
||||
void cutsceneBattleForceMoveStart(
|
||||
const cutsceneitem_t *item,
|
||||
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 data Runtime data storage.
|
||||
* @returns true always.
|
||||
*/
|
||||
bool_t cutsceneBattleForceActionUpdate(
|
||||
bool_t cutsceneBattleForceMoveUpdate(
|
||||
const cutsceneitem_t *item,
|
||||
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 item Destination item, already zeroed by the caller.
|
||||
* @return Error code indicating success or failure of the parse.
|
||||
*/
|
||||
errorret_t cutsceneBattleForceActionLoad(
|
||||
errorret_t cutsceneBattleForceMoveLoad(
|
||||
yyjson_val *itemObj,
|
||||
cutsceneitem_t *item
|
||||
);
|
||||
@@ -183,7 +183,7 @@ errorret_t cutsceneStartBattleLoad(
|
||||
yyjson_val *abilityVal;
|
||||
size_t a = 0;
|
||||
while((abilityVal = yyjson_arr_iter_next(&abilityIter)) != NULL) {
|
||||
battleability_t ability;
|
||||
battlemoveid_t ability;
|
||||
if(!cutsceneJsonLookupBattleAbility(
|
||||
yyjson_get_str(abilityVal), &ability
|
||||
)) {
|
||||
|
||||
@@ -16,7 +16,7 @@ typedef struct {
|
||||
battlefighterstats_t stats;
|
||||
uint16_t healthMax;
|
||||
uint16_t mpMax;
|
||||
battleability_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||
battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX];
|
||||
} cutscenestartbattleenemy_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -17,27 +17,7 @@
|
||||
cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
[CUTSCENE_ITEM_TYPE_NULL] = { 0 },
|
||||
|
||||
[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
|
||||
},
|
||||
|
||||
// Control
|
||||
[CUTSCENE_ITEM_TYPE_CALLBACK] = {
|
||||
.init = cutsceneCallbackStart,
|
||||
.update = cutsceneCallbackUpdate
|
||||
@@ -57,25 +37,11 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.load = cutsceneCutsceneLoad
|
||||
},
|
||||
|
||||
[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_FADE] = {
|
||||
.init = cutsceneFadeStart,
|
||||
.update = cutsceneFadeUpdate,
|
||||
.jsonName = "FADE",
|
||||
.load = cutsceneFadeLoad
|
||||
[CUTSCENE_ITEM_TYPE_INSERT] = {
|
||||
.init = cutsceneInsertStart,
|
||||
.update = cutsceneInsertUpdate,
|
||||
.jsonName = "INSERT",
|
||||
.load = cutsceneInsertLoad
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_SET_PAUSE] = {
|
||||
@@ -90,122 +56,6 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.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] = {
|
||||
.init = cutsceneMarkerStart,
|
||||
.update = cutsceneMarkerUpdate,
|
||||
@@ -234,6 +84,194 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.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] = {
|
||||
.init = cutsceneSaveDeviceCheckStart,
|
||||
.update = cutsceneSaveDeviceCheckUpdate,
|
||||
@@ -248,11 +286,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.load = cutsceneSaveLoadAllSlotsLoad
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_KEYBOARD] = {
|
||||
.init = cutsceneKeyboardStart,
|
||||
.update = cutsceneKeyboardUpdate
|
||||
},
|
||||
|
||||
// Audio
|
||||
[CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = {
|
||||
.init = cutsceneAudioPlayStart,
|
||||
.update = cutsceneAudioPlayUpdate,
|
||||
@@ -315,32 +349,7 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
||||
.load = cutsceneAudioSetLoad
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_IDLE] = {
|
||||
.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
|
||||
},
|
||||
|
||||
// Story
|
||||
[CUTSCENE_ITEM_TYPE_STORY_SET_FLAG] = {
|
||||
.init = cutsceneStorySetFlagStart,
|
||||
.update = cutsceneStorySetFlagUpdate,
|
||||
@@ -506,8 +515,9 @@ void cutsceneInsertResolveAndSplice(const char_t *name) {
|
||||
// nothing needs to keep the doc itself resident past the parse.
|
||||
cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX];
|
||||
cutscene_t scene = { .items = items };
|
||||
errorret_t parseResult =
|
||||
cutsceneParseDoc(entry->data.json, &scene, items, CUTSCENE_INSERT_ITEMS_MAX);
|
||||
errorret_t parseResult = cutsceneParseDoc(
|
||||
entry->data.json, &scene, items, CUTSCENE_INSERT_ITEMS_MAX
|
||||
);
|
||||
assetUnlockEntry(entry);
|
||||
if(errorIsNotOk(parseResult)) {
|
||||
errorCatch(errorPrint(parseResult));
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#include "maparea/cutscenemapareawait.h"
|
||||
#include "battle/cutscenestartbattle.h"
|
||||
#include "battle/cutscenebattlewaitstate.h"
|
||||
#include "battle/cutscenebattleforceaction.h"
|
||||
#include "battle/cutscenebattleforcemove.h"
|
||||
#include "battle/cutsceneregularbattle.h"
|
||||
#include "save/cutscenesavedevicecheck.h"
|
||||
#include "save/cutscenesaveloadallslots.h"
|
||||
@@ -120,7 +120,7 @@ struct cutsceneitem_s {
|
||||
cutsceneemoji_t emoji;
|
||||
cutsceneshake_t shake;
|
||||
cutscenebattlewaitstate_t battleWaitState;
|
||||
cutscenebattleforceaction_t battleForceAction;
|
||||
cutscenebattleforcemove_t battleForceMove;
|
||||
cutscenemodal_t modal;
|
||||
cutscenemodaloptionsmarkers_t modalOptionsMarkers;
|
||||
cutsceneprint_t print;
|
||||
|
||||
@@ -16,41 +16,60 @@ typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
||||
typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_NULL,
|
||||
|
||||
CUTSCENE_ITEM_TYPE_TEXT,
|
||||
CUTSCENE_ITEM_TYPE_TEXT_MINI,
|
||||
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
|
||||
// Control
|
||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||
CUTSCENE_ITEM_TYPE_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_CUTSCENE,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT,
|
||||
CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO,
|
||||
CUTSCENE_ITEM_TYPE_FADE,
|
||||
CUTSCENE_ITEM_TYPE_INSERT,
|
||||
CUTSCENE_ITEM_TYPE_SET_PAUSE,
|
||||
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_RESTART,
|
||||
CUTSCENE_ITEM_TYPE_QUIT_GAME,
|
||||
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_LOAD_ALL_SLOTS,
|
||||
CUTSCENE_ITEM_TYPE_KEYBOARD,
|
||||
|
||||
// Audio
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_PLAY,
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_STOP,
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_PAUSE,
|
||||
@@ -60,10 +79,8 @@ typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN,
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP,
|
||||
CUTSCENE_ITEM_TYPE_AUDIO_SET,
|
||||
CUTSCENE_ITEM_TYPE_IDLE,
|
||||
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
||||
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
||||
CUTSCENE_ITEM_TYPE_INSERT,
|
||||
|
||||
// Story
|
||||
CUTSCENE_ITEM_TYPE_STORY_SET_FLAG,
|
||||
CUTSCENE_ITEM_TYPE_STORY_WAIT_FLAG,
|
||||
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
#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_ABILITY_FIRE },
|
||||
{ "FIRE", BATTLE_MOVE_ID_FIRE },
|
||||
};
|
||||
#define CUTSCENE_JSON_BATTLE_ABILITY_TABLE_COUNT \
|
||||
(sizeof(CUTSCENE_JSON_BATTLE_ABILITY_TABLE) / \
|
||||
@@ -17,7 +21,7 @@ static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_ABILITY_TABLE[] = {
|
||||
|
||||
bool_t cutsceneJsonLookupBattleAbility(
|
||||
const char_t *name,
|
||||
battleability_t *out
|
||||
battlemoveid_t *out
|
||||
) {
|
||||
int32_t value;
|
||||
if(!cutsceneJsonLookupEnumInsensitive(
|
||||
@@ -26,6 +30,6 @@ bool_t cutsceneJsonLookupBattleAbility(
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
*out = (battleability_t)value;
|
||||
*out = (battlemoveid_t)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "rpg/battle/battleability.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 ability on success.
|
||||
* @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,
|
||||
battleability_t *out
|
||||
battlemoveid_t *out
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ ENTITY_GLOBAL_CALLBACK(3) {
|
||||
|
||||
ENTITY_GLOBAL_CALLBACK(4) {
|
||||
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[] = {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "rpg/item/item.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "rpg/battle/party.h"
|
||||
#include "rpg/battle/move/battlemove.h"
|
||||
#include "save/save.h"
|
||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||
#include "time/time.h"
|
||||
@@ -30,6 +31,7 @@ errorret_t rpgInit(void) {
|
||||
|
||||
errorChain(storyInit());
|
||||
errorChain(itemInit());
|
||||
errorChain(battleMoveInit());
|
||||
backpackInit();
|
||||
partyInit();
|
||||
cutsceneSystemInit();
|
||||
|
||||
+23
-119
@@ -9,7 +9,10 @@
|
||||
#include "rpg/battle/battle.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.
|
||||
static cutsceneitem_t CUTSCENE_TEST_SCRATCH_ITEMS[] = {
|
||||
// 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 },
|
||||
// enemy (slot 2) forced onto allyA (slot 0)
|
||||
{
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_ACTION,
|
||||
.battleForceAction = { .fighterIndex = 2, .targetIndex = 0 }
|
||||
.type = CUTSCENE_ITEM_TYPE_BATTLE_FORCE_MOVE,
|
||||
.battleForceMove = { .fighterIndex = 2, .targetIndex = 0 }
|
||||
},
|
||||
{ .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_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.
|
||||
battleQueueAction(ally2->id, BATTLE_ACTION_ATTACK, enemy->id);
|
||||
battleQueueMove(ally2->id, BATTLE_MOVE_ID_ATTACK, enemy->id);
|
||||
|
||||
battlePlayerAttack(enemy->id);
|
||||
assert_int_equal(BATTLE.actions[ally1->id].type, BATTLE_ACTION_ATTACK);
|
||||
assert_int_equal(BATTLE.actions[ally1->id].targetIndex, enemy->id);
|
||||
assert_int_equal(BATTLE.moves[ally1->id].move, BATTLE_MOVE_ID_ATTACK);
|
||||
assert_int_equal(BATTLE.moves[ally1->id].targetIndex, enemy->id);
|
||||
|
||||
// ally2 already had a decision queued, and enemy is AI-controlled, so
|
||||
// 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
|
||||
assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING);
|
||||
assert_int_equal(BATTLE.actions[enemy->id].type, BATTLE_ACTION_ATTACK);
|
||||
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, ally->id);
|
||||
assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK);
|
||||
assert_int_equal(BATTLE.moves[enemy->id].targetIndex, ally->id);
|
||||
}
|
||||
|
||||
static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles(
|
||||
@@ -278,107 +281,12 @@ static void test_battleFleeUnavailableIsIgnored(void **state) {
|
||||
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) +
|
||||
// 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
|
||||
// partially-scripted round actually works end-to-end (not just that each
|
||||
// item type compiles).
|
||||
static void test_battleCutsceneForceActionOverridesTarget(void **state) {
|
||||
static void test_battleCutsceneForceMoveOverridesTarget(void **state) {
|
||||
|
||||
|
||||
battleInit();
|
||||
@@ -411,22 +319,22 @@ static void test_battleCutsceneForceActionOverridesTarget(void **state) {
|
||||
assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND);
|
||||
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
|
||||
|
||||
assert_int_equal(BATTLE.actions[enemy->id].type, BATTLE_ACTION_ATTACK);
|
||||
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, allyA->id);
|
||||
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_ACTION done -> SET_PAUSE(NONE) lifts it
|
||||
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 action from three frames ago survived untouched.
|
||||
assert_int_equal(BATTLE.actions[enemy->id].type, BATTLE_ACTION_ATTACK);
|
||||
assert_int_equal(BATTLE.actions[enemy->id].targetIndex, allyA->id);
|
||||
// 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);
|
||||
@@ -437,7 +345,7 @@ static void test_battleCutsceneForceActionOverridesTarget(void **state) {
|
||||
// AI_SELECTION: enemy already decided, skipped -> MOVES_EXECUTING
|
||||
battleUpdate();
|
||||
// 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();// allyB attacks enemy
|
||||
@@ -458,11 +366,7 @@ int main(int argc, char** argv) {
|
||||
cmocka_unit_test(test_battleLossEndsInStateEnded),
|
||||
cmocka_unit_test(test_battleFleeEndsInStateEndedImmediately),
|
||||
cmocka_unit_test(test_battleFleeUnavailableIsIgnored),
|
||||
cmocka_unit_test(test_battleOnStateChangedFiresAcrossFullRound),
|
||||
cmocka_unit_test(
|
||||
test_battleOnActionDecidedFiresPerFighterRegardlessOfSource
|
||||
),
|
||||
cmocka_unit_test(test_battleCutsceneForceActionOverridesTarget),
|
||||
cmocka_unit_test(test_battleCutsceneForceMoveOverridesTarget),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
||||
@@ -49,7 +49,7 @@ import zipfile
|
||||
import zlib
|
||||
import io
|
||||
|
||||
MAGIC = b'DSK2'
|
||||
MAGIC = b'DSK'
|
||||
VERSION = 2
|
||||
HEADER_FORMAT = '<4sIII'
|
||||
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
||||
|
||||
Reference in New Issue
Block a user