Remove story and battle stuff
This commit is contained in:
@@ -11,10 +11,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(battle)
|
|
||||||
add_subdirectory(cutscene)
|
add_subdirectory(cutscene)
|
||||||
add_subdirectory(entity)
|
add_subdirectory(entity)
|
||||||
add_subdirectory(overworld)
|
add_subdirectory(overworld)
|
||||||
|
|
||||||
add_subdirectory(story)
|
|
||||||
add_subdirectory(item)
|
add_subdirectory(item)
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
|
||||||
battle.c
|
|
||||||
battlefighter.c
|
|
||||||
party.c
|
|
||||||
)
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "battle.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t battleGetAvailableFighter(void) {
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(BATTLE.fighters[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleAddFighter(
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
|
||||||
const uint8_t index = battleGetAvailableFighter();
|
|
||||||
if(index == 0xFF) return NULL;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[index];
|
|
||||||
battleFighterInit(fighter, team, controller, stats, healthMax, mpMax);
|
|
||||||
return fighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleStart(
|
|
||||||
const battleencountertype_t encounterType,
|
|
||||||
const bool_t fleeAvailable
|
|
||||||
) {
|
|
||||||
assertTrue(encounterType < BATTLE_ENCOUNTER_COUNT, "Invalid encounter type");
|
|
||||||
|
|
||||||
BATTLE.encounterType = encounterType;
|
|
||||||
BATTLE.fleeAvailable = fleeAvailable;
|
|
||||||
BATTLE.result = BATTLE_RESULT_NONE;
|
|
||||||
BATTLE.round = 1;
|
|
||||||
BATTLE.turnIndex = 0;
|
|
||||||
battleBuildTurnOrder(true);
|
|
||||||
|
|
||||||
BATTLE.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleDispose(void) {
|
|
||||||
battleInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleGetCurrentFighter(void) {
|
|
||||||
if(!BATTLE.active) return NULL;
|
|
||||||
if(BATTLE.turnIndex >= BATTLE.turnCount) return NULL;
|
|
||||||
return &BATTLE.fighters[BATTLE.turnOrder[BATTLE.turnIndex]];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t battleGetAliveCount(const battlefighterteam_t team) {
|
|
||||||
uint8_t count = 0;
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(BATTLE.fighters[i].team != team) continue;
|
|
||||||
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleResolveAttack(
|
|
||||||
battlefighter_t *attacker,
|
|
||||||
battlefighter_t *defender
|
|
||||||
) {
|
|
||||||
assertNotNull(attacker, "Attacker cannot be NULL");
|
|
||||||
assertNotNull(defender, "Defender cannot be NULL");
|
|
||||||
|
|
||||||
const int32_t rawDamage =
|
|
||||||
(int32_t)attacker->stats.attack - (int32_t)defender->stats.defense;
|
|
||||||
const uint16_t damage = rawDamage > 0 ? (uint16_t)rawDamage : 1;
|
|
||||||
|
|
||||||
defender->health = damage >= defender->health ? 0 : defender->health - damage;
|
|
||||||
if(defender->health == 0) defender->status = BATTLE_FIGHTER_STATUS_DEAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleNextTurn(void) {
|
|
||||||
BATTLE.turnIndex++;
|
|
||||||
if(BATTLE.turnIndex < BATTLE.turnCount) return;
|
|
||||||
|
|
||||||
BATTLE.round++;
|
|
||||||
BATTLE.turnIndex = 0;
|
|
||||||
battleBuildTurnOrder(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
battleresult_t battleCheckResult(void) {
|
|
||||||
if(BATTLE.result != BATTLE_RESULT_NONE) return BATTLE.result;
|
|
||||||
|
|
||||||
if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ALLY) == 0) {
|
|
||||||
BATTLE.result = BATTLE_RESULT_LOSS;
|
|
||||||
} else if(battleGetAliveCount(BATTLE_FIGHTER_TEAM_ENEMY) == 0) {
|
|
||||||
BATTLE.result = BATTLE_RESULT_WIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BATTLE.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battlePlayerAttack(const uint8_t targetIndex) {
|
|
||||||
battlefighter_t *attacker = battleGetCurrentFighter();
|
|
||||||
if(attacker == NULL) return;
|
|
||||||
if(attacker->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) return;
|
|
||||||
if(targetIndex >= BATTLE_FIGHTER_COUNT_MAX) return;
|
|
||||||
|
|
||||||
battlefighter_t *defender = &BATTLE.fighters[targetIndex];
|
|
||||||
if(!battleFighterIsAlive(defender)) return;
|
|
||||||
|
|
||||||
battleResolveAttack(attacker, defender);
|
|
||||||
battleCheckResult();
|
|
||||||
if(BATTLE.result == BATTLE_RESULT_NONE) battleNextTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
void battlePlayerFlee(void) {
|
|
||||||
battlefighter_t *fighter = battleGetCurrentFighter();
|
|
||||||
if(fighter == NULL) return;
|
|
||||||
if(fighter->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) return;
|
|
||||||
if(!BATTLE.fleeAvailable) return;
|
|
||||||
|
|
||||||
BATTLE.result = BATTLE_RESULT_FLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleUpdate(void) {
|
|
||||||
if(!BATTLE.active) return;
|
|
||||||
if(BATTLE.result != BATTLE_RESULT_NONE) return;
|
|
||||||
|
|
||||||
battlefighter_t *current = battleGetCurrentFighter();
|
|
||||||
if(current == NULL) return;
|
|
||||||
|
|
||||||
if(!battleFighterIsAlive(current)) {
|
|
||||||
battleNextTurn();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(current->controller != BATTLE_FIGHTER_CONTROLLER_AI) return;
|
|
||||||
|
|
||||||
battlefighter_t *target = battleAIChooseTarget(current);
|
|
||||||
if(target != NULL) battleResolveAttack(current, target);
|
|
||||||
|
|
||||||
battleCheckResult();
|
|
||||||
if(BATTLE.result == BATTLE_RESULT_NONE) battleNextTurn();
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleBuildTurnOrder(const bool_t applyEncounterBias) {
|
|
||||||
BATTLE.turnCount = 0;
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
if(!battleFighterIsAlive(&BATTLE.fighters[i])) continue;
|
|
||||||
BATTLE.turnOrder[BATTLE.turnCount++] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insertion sort by speed descending -- fine for BATTLE_FIGHTER_COUNT_MAX.
|
|
||||||
for(uint8_t i = 1; i < BATTLE.turnCount; i++) {
|
|
||||||
const uint8_t key = BATTLE.turnOrder[i];
|
|
||||||
const uint16_t keySpeed = BATTLE.fighters[key].stats.speed;
|
|
||||||
|
|
||||||
int8_t j = (int8_t)i - 1;
|
|
||||||
while(
|
|
||||||
j >= 0 && BATTLE.fighters[BATTLE.turnOrder[j]].stats.speed < keySpeed
|
|
||||||
) {
|
|
||||||
BATTLE.turnOrder[j + 1] = BATTLE.turnOrder[j];
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
BATTLE.turnOrder[j + 1] = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!applyEncounterBias) return;
|
|
||||||
|
|
||||||
if(BATTLE.encounterType == BATTLE_ENCOUNTER_PLAYER_ADVANTAGE) {
|
|
||||||
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ALLY);
|
|
||||||
} else if(BATTLE.encounterType == BATTLE_ENCOUNTER_BACK_ATTACK) {
|
|
||||||
battleMoveTeamFirst(BATTLE_FIGHTER_TEAM_ENEMY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void battleMoveTeamFirst(const battlefighterteam_t team) {
|
|
||||||
uint8_t sorted[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
uint8_t count = 0;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE.turnCount; i++) {
|
|
||||||
if(BATTLE.fighters[BATTLE.turnOrder[i]].team != team) continue;
|
|
||||||
sorted[count++] = BATTLE.turnOrder[i];
|
|
||||||
}
|
|
||||||
for(uint8_t i = 0; i < BATTLE.turnCount; i++) {
|
|
||||||
if(BATTLE.fighters[BATTLE.turnOrder[i]].team == team) continue;
|
|
||||||
sorted[count++] = BATTLE.turnOrder[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryCopy(BATTLE.turnOrder, sorted, sizeof(uint8_t) * BATTLE.turnCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter) {
|
|
||||||
const battlefighterteam_t enemyTeam =
|
|
||||||
fighter->team == BATTLE_FIGHTER_TEAM_ALLY ?
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY;
|
|
||||||
|
|
||||||
battlefighter_t *weakest = NULL;
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
battlefighter_t *candidate = &BATTLE.fighters[i];
|
|
||||||
if(candidate->team != enemyTeam) continue;
|
|
||||||
if(!battleFighterIsAlive(candidate)) continue;
|
|
||||||
if(weakest == NULL || candidate->health < weakest->health) {
|
|
||||||
weakest = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return weakest;
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "battlefighter.h"
|
|
||||||
|
|
||||||
#define BATTLE_FIGHTER_COUNT_MAX 8
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
BATTLE_ENCOUNTER_REGULAR,
|
|
||||||
BATTLE_ENCOUNTER_PLAYER_ADVANTAGE,
|
|
||||||
BATTLE_ENCOUNTER_BACK_ATTACK,
|
|
||||||
|
|
||||||
BATTLE_ENCOUNTER_COUNT
|
|
||||||
} battleencountertype_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
BATTLE_RESULT_NONE,
|
|
||||||
BATTLE_RESULT_WIN,
|
|
||||||
BATTLE_RESULT_LOSS,
|
|
||||||
BATTLE_RESULT_FLED,
|
|
||||||
|
|
||||||
BATTLE_RESULT_COUNT
|
|
||||||
} battleresult_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool_t active;
|
|
||||||
battlefighter_t fighters[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
|
|
||||||
battleencountertype_t encounterType;
|
|
||||||
bool_t fleeAvailable;
|
|
||||||
battleresult_t result;
|
|
||||||
|
|
||||||
// Fighter indices (into fighters[]), sorted for the current round.
|
|
||||||
uint8_t turnOrder[BATTLE_FIGHTER_COUNT_MAX];
|
|
||||||
uint8_t turnCount;
|
|
||||||
uint8_t turnIndex;
|
|
||||||
uint16_t round;
|
|
||||||
} battle_t;
|
|
||||||
|
|
||||||
extern battle_t BATTLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the battle system. Marks it as inactive with no fighters.
|
|
||||||
*/
|
|
||||||
void battleInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an available (unused) fighter slot index.
|
|
||||||
*
|
|
||||||
* @return The index of an available fighter slot, or 0xFF if none are
|
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
uint8_t battleGetAvailableFighter(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a fighter to the battle in the next available slot.
|
|
||||||
*
|
|
||||||
* @param team The team the fighter belongs to.
|
|
||||||
* @param controller Who makes decisions for the fighter.
|
|
||||||
* @param stats The fighter's base combat stats.
|
|
||||||
* @param healthMax The fighter's maximum health.
|
|
||||||
* @param mpMax The fighter's maximum mp.
|
|
||||||
* @return Pointer to the newly added fighter, or NULL if the battle is
|
|
||||||
* already full.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleAddFighter(
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the battle: builds the opening turn order (biased by
|
|
||||||
* encounterType for the first round only) and marks the battle active.
|
|
||||||
* Call once every fighter has been added via battleAddFighter.
|
|
||||||
*
|
|
||||||
* @param encounterType Determines the opening round's turn order.
|
|
||||||
* @param fleeAvailable Whether the party may attempt to flee this battle.
|
|
||||||
*/
|
|
||||||
void battleStart(
|
|
||||||
const battleencountertype_t encounterType,
|
|
||||||
const bool_t fleeAvailable
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the battle, clearing all fighters and marking it inactive.
|
|
||||||
*/
|
|
||||||
void battleDispose(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fighter whose turn it currently is.
|
|
||||||
*
|
|
||||||
* @return Pointer to the active fighter, or NULL if the battle isn't
|
|
||||||
* active or has no living fighters left to act.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleGetCurrentFighter(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of living fighters on a team.
|
|
||||||
*
|
|
||||||
* @param team The team to count.
|
|
||||||
* @return Count of living fighters on that team.
|
|
||||||
*/
|
|
||||||
uint8_t battleGetAliveCount(const battlefighterteam_t team);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a physical attack from attacker onto defender: damage is the
|
|
||||||
* attacker's attack stat minus the defender's defense stat (minimum 1),
|
|
||||||
* subtracted from the defender's health. The defender is marked dead
|
|
||||||
* once health reaches 0.
|
|
||||||
*
|
|
||||||
* @param attacker The attacking fighter.
|
|
||||||
* @param defender The defending fighter.
|
|
||||||
*/
|
|
||||||
void battleResolveAttack(
|
|
||||||
battlefighter_t *attacker,
|
|
||||||
battlefighter_t *defender
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ends the current fighter's turn and advances to the next fighter in
|
|
||||||
* the turn order, starting a new round (rebuilding turn order purely by
|
|
||||||
* speed) once every fighter in the current round has acted.
|
|
||||||
*/
|
|
||||||
void battleNextTurn(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the battle has been won or lost, updating and
|
|
||||||
* returning BATTLE.result. Does nothing if a result has already been
|
|
||||||
* set (e.g. by a successful flee).
|
|
||||||
*
|
|
||||||
* @return The battle's current result.
|
|
||||||
*/
|
|
||||||
battleresult_t battleCheckResult(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits the current fighter's attack against a target, if it is
|
|
||||||
* currently a player-controlled fighter's turn. Resolves the attack,
|
|
||||||
* checks for a battle result, and advances the turn.
|
|
||||||
*
|
|
||||||
* @param targetIndex Index into BATTLE.fighters of the target.
|
|
||||||
*/
|
|
||||||
void battlePlayerAttack(const uint8_t targetIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits a flee attempt for the current fighter's turn, if it is
|
|
||||||
* currently a player-controlled fighter's turn and fleeing is
|
|
||||||
* available for this battle. Always succeeds, ending the battle with
|
|
||||||
* BATTLE_RESULT_FLED.
|
|
||||||
*/
|
|
||||||
void battlePlayerFlee(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the battle simulation for one frame: resolves the current
|
|
||||||
* fighter's turn automatically if AI-controlled, otherwise waits for a
|
|
||||||
* player action via battlePlayerAttack/battlePlayerFlee. No-op if the
|
|
||||||
* battle isn't active or already has a result.
|
|
||||||
*/
|
|
||||||
void battleUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds BATTLE.turnOrder/turnCount from every currently living
|
|
||||||
* fighter, sorted by speed descending.
|
|
||||||
*
|
|
||||||
* @param applyEncounterBias If true, reorders the freshly speed-sorted
|
|
||||||
* queue so BATTLE.encounterType's favoured team goes first (used only
|
|
||||||
* for the opening round).
|
|
||||||
*/
|
|
||||||
void battleBuildTurnOrder(const bool_t applyEncounterBias);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stably partitions BATTLE.turnOrder so every fighter on the given team
|
|
||||||
* comes first, preserving each side's relative (speed-sorted) order.
|
|
||||||
*
|
|
||||||
* @param team The team to move to the front of the turn order.
|
|
||||||
*/
|
|
||||||
void battleMoveTeamFirst(const battlefighterteam_t team);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Picks an AI target for fighter: the lowest-health living fighter on
|
|
||||||
* the opposing team.
|
|
||||||
*
|
|
||||||
* @param fighter The AI-controlled fighter choosing a target.
|
|
||||||
* @return The chosen target, or NULL if the opposing team has no
|
|
||||||
* living fighters.
|
|
||||||
*/
|
|
||||||
battlefighter_t *battleAIChooseTarget(const battlefighter_t *fighter);
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "battlefighter.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
void battleFighterInit(
|
|
||||||
battlefighter_t *fighter,
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
|
||||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
|
||||||
assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team");
|
|
||||||
assertTrue(
|
|
||||||
controller < BATTLE_FIGHTER_CONTROLLER_COUNT,
|
|
||||||
"Invalid fighter controller"
|
|
||||||
);
|
|
||||||
|
|
||||||
const uint8_t id = fighter->id;
|
|
||||||
memoryZero(fighter, sizeof(battlefighter_t));
|
|
||||||
fighter->id = id;
|
|
||||||
fighter->status = BATTLE_FIGHTER_STATUS_NORMAL;
|
|
||||||
fighter->team = team;
|
|
||||||
fighter->controller = controller;
|
|
||||||
fighter->stats = stats;
|
|
||||||
fighter->healthMax = healthMax;
|
|
||||||
fighter->health = healthMax;
|
|
||||||
fighter->mpMax = mpMax;
|
|
||||||
fighter->mp = mpMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter) {
|
|
||||||
assertNotNull(fighter, "Fighter pointer cannot be NULL");
|
|
||||||
return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL;
|
|
||||||
}
|
|
||||||
@@ -1,87 +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"
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
uint8_t id;
|
|
||||||
battlefighterstatus_t status;
|
|
||||||
battlefighterteam_t team;
|
|
||||||
battlefightercontroller_t controller;
|
|
||||||
|
|
||||||
uint16_t health;
|
|
||||||
uint16_t healthMax;
|
|
||||||
uint16_t mp;
|
|
||||||
uint16_t mpMax;
|
|
||||||
|
|
||||||
battlefighterstats_t stats;
|
|
||||||
} battlefighter_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a battle fighter in place, filling health/mp to their maximum
|
|
||||||
* values and setting its status to normal.
|
|
||||||
*
|
|
||||||
* @param fighter Pointer to the fighter to initialize.
|
|
||||||
* @param team The team the fighter belongs to.
|
|
||||||
* @param controller Who makes decisions for the fighter.
|
|
||||||
* @param stats The fighter's base combat stats.
|
|
||||||
* @param healthMax The fighter's maximum health.
|
|
||||||
* @param mpMax The fighter's maximum mp.
|
|
||||||
*/
|
|
||||||
void battleFighterInit(
|
|
||||||
battlefighter_t *fighter,
|
|
||||||
const battlefighterteam_t team,
|
|
||||||
const battlefightercontroller_t controller,
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the fighter is in a state where it can still act (i.e.
|
|
||||||
* is not dead).
|
|
||||||
*
|
|
||||||
* @param fighter Pointer to the fighter to check.
|
|
||||||
* @returns True if the fighter can act.
|
|
||||||
*/
|
|
||||||
bool_t battleFighterIsAlive(const battlefighter_t *fighter);
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "party.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
party_t PARTY;
|
|
||||||
|
|
||||||
void partyInit(void) {
|
|
||||||
memoryZero(&PARTY, sizeof(party_t));
|
|
||||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
|
||||||
PARTY.members[i].id = i;
|
|
||||||
}
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
|
||||||
PARTY.order[i] = PARTY_ORDER_EMPTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t partyGetAvailableMember(void) {
|
|
||||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
|
||||||
if(PARTY.members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *partyAddMember(
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
) {
|
|
||||||
const uint8_t index = partyGetAvailableMember();
|
|
||||||
if(index == 0xFF) return NULL;
|
|
||||||
|
|
||||||
battlefighter_t *member = &PARTY.members[index];
|
|
||||||
battleFighterInit(
|
|
||||||
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
|
||||||
stats, healthMax, mpMax
|
|
||||||
);
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
|
||||||
if(PARTY.order[i] != PARTY_ORDER_EMPTY) continue;
|
|
||||||
PARTY.order[i] = index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return member;
|
|
||||||
}
|
|
||||||
|
|
||||||
battlefighter_t *partyGetOrderMember(const uint8_t slot) {
|
|
||||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
|
||||||
|
|
||||||
const uint8_t index = PARTY.order[slot];
|
|
||||||
if(index == PARTY_ORDER_EMPTY) return NULL;
|
|
||||||
return &PARTY.members[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex) {
|
|
||||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
|
||||||
assertTrue(
|
|
||||||
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
|
||||||
"Invalid party member index"
|
|
||||||
);
|
|
||||||
|
|
||||||
PARTY.order[slot] = memberIndex;
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "battlefighter.h"
|
|
||||||
|
|
||||||
#define PARTY_MEMBER_COUNT_MAX 4
|
|
||||||
#define PARTY_ACTIVE_SIZE_MAX 3
|
|
||||||
#define PARTY_ORDER_EMPTY 0xFF
|
|
||||||
|
|
||||||
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];
|
|
||||||
} party_t;
|
|
||||||
|
|
||||||
extern party_t PARTY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the party system with an empty roster and order.
|
|
||||||
*/
|
|
||||||
void partyInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an available (unused) party member slot index.
|
|
||||||
*
|
|
||||||
* @return The index of an available slot, or 0xFF if the party is full.
|
|
||||||
*/
|
|
||||||
uint8_t partyGetAvailableMember(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a member to the party roster in the next available slot. Party
|
|
||||||
* members are always allies controlled by the player. If there is a
|
|
||||||
* free active order slot, the new member is placed into it.
|
|
||||||
*
|
|
||||||
* @param stats The member's base combat stats.
|
|
||||||
* @param healthMax The member's maximum health.
|
|
||||||
* @param mpMax The member's maximum mp.
|
|
||||||
* @return Pointer to the newly added party member, or NULL if the party is
|
|
||||||
* already full.
|
|
||||||
*/
|
|
||||||
battlefighter_t *partyAddMember(
|
|
||||||
const battlefighterstats_t stats,
|
|
||||||
const uint16_t healthMax,
|
|
||||||
const uint16_t mpMax
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the roster member currently occupying an active order slot.
|
|
||||||
*
|
|
||||||
* @param slot The active order slot to query.
|
|
||||||
* @return Pointer to the member in that slot, or NULL if the slot is
|
|
||||||
* empty.
|
|
||||||
*/
|
|
||||||
battlefighter_t *partyGetOrderMember(const uint8_t slot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a roster member to an active order slot, replacing whatever
|
|
||||||
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
|
||||||
*
|
|
||||||
* @param slot The active order slot to assign.
|
|
||||||
* @param memberIndex The roster member index to place there, or
|
|
||||||
* PARTY_ORDER_EMPTY to clear the slot.
|
|
||||||
*/
|
|
||||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex);
|
|
||||||
@@ -14,4 +14,3 @@ add_subdirectory(entity)
|
|||||||
add_subdirectory(item)
|
add_subdirectory(item)
|
||||||
add_subdirectory(maparea)
|
add_subdirectory(maparea)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
add_subdirectory(battle)
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
cutscenestartbattle.c
|
|
||||||
)
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
|
||||||
#include "rpg/battle/party.h"
|
|
||||||
#include "scene/scene.h"
|
|
||||||
|
|
||||||
void cutsceneStartBattleStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
const cutscenestartbattle_t *config = &item->startBattle;
|
|
||||||
|
|
||||||
battleInit();
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
|
||||||
battlefighter_t *member = partyGetOrderMember(i);
|
|
||||||
if(member == NULL) continue;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = battleAddFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
|
||||||
member->stats, member->healthMax, member->mpMax
|
|
||||||
);
|
|
||||||
if(fighter == NULL) continue;
|
|
||||||
|
|
||||||
fighter->health = member->health;
|
|
||||||
fighter->mp = member->mp;
|
|
||||||
fighter->status = member->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < config->enemyCount; i++) {
|
|
||||||
const cutscenestartbattleenemy_t *enemy = &config->enemies[i];
|
|
||||||
battleAddFighter(
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI,
|
|
||||||
enemy->stats, enemy->healthMax, enemy->mpMax
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
battleStart(config->encounterType, config->fleeAvailable);
|
|
||||||
sceneSet(SCENE_TYPE_BATTLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t cutsceneStartBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
) {
|
|
||||||
if(BATTLE.result == BATTLE_RESULT_NONE) return false;
|
|
||||||
|
|
||||||
// Sync ally HP/MP back to the persistent party roster. Relies on
|
|
||||||
// ally fighters having been added to BATTLE.fighters in the same
|
|
||||||
// order partyGetOrderMember() iterates, starting at index 0 (see
|
|
||||||
// cutsceneStartBattleStart).
|
|
||||||
uint8_t allySlot = 0;
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
|
||||||
battlefighter_t *member = partyGetOrderMember(i);
|
|
||||||
if(member == NULL) continue;
|
|
||||||
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[allySlot++];
|
|
||||||
member->health = fighter->health;
|
|
||||||
member->mp = fighter->mp;
|
|
||||||
member->status = fighter->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneSet(SCENE_TYPE_OVERWORLD);
|
|
||||||
battleDispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
|
|
||||||
#define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battlefighterstats_t stats;
|
|
||||||
uint16_t healthMax;
|
|
||||||
uint16_t mpMax;
|
|
||||||
} cutscenestartbattleenemy_t;
|
|
||||||
|
|
||||||
typedef struct cutsceneitem_s cutsceneitem_t;
|
|
||||||
typedef union cutsceneitemdata_u cutsceneitemdata_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
battleencountertype_t encounterType;
|
|
||||||
bool_t fleeAvailable;
|
|
||||||
uint8_t enemyCount;
|
|
||||||
cutscenestartbattleenemy_t enemies[CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX];
|
|
||||||
} cutscenestartbattle_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a battle: seeds BATTLE with the party's active order members
|
|
||||||
* and the item's configured enemies, then switches to the battle
|
|
||||||
* scene.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
*/
|
|
||||||
void cutsceneStartBattleStart(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for the battle to produce a result, syncs ally HP/MP back to
|
|
||||||
* the party roster, then returns to the overworld scene.
|
|
||||||
*
|
|
||||||
* @param item The cutscene item.
|
|
||||||
* @param data Runtime data storage.
|
|
||||||
* @returns true once the battle has ended.
|
|
||||||
*/
|
|
||||||
bool_t cutsceneStartBattleUpdate(
|
|
||||||
const cutsceneitem_t *item,
|
|
||||||
cutsceneitemdata_t *data
|
|
||||||
);
|
|
||||||
@@ -105,11 +105,6 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
|||||||
.update = cutsceneMapAreaWaitUpdate
|
.update = cutsceneMapAreaWaitUpdate
|
||||||
},
|
},
|
||||||
|
|
||||||
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
|
|
||||||
.init = cutsceneStartBattleStart,
|
|
||||||
.update = cutsceneStartBattleUpdate
|
|
||||||
},
|
|
||||||
|
|
||||||
[CUTSCENE_ITEM_TYPE_EMOJI] = {
|
[CUTSCENE_ITEM_TYPE_EMOJI] = {
|
||||||
.init = cutsceneEmojiStart,
|
.init = cutsceneEmojiStart,
|
||||||
.update = cutsceneEmojiUpdate
|
.update = cutsceneEmojiUpdate
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#include "maparea/cutscenemapareaadd.h"
|
#include "maparea/cutscenemapareaadd.h"
|
||||||
#include "maparea/cutscenemaparearemove.h"
|
#include "maparea/cutscenemaparearemove.h"
|
||||||
#include "maparea/cutscenemapareawait.h"
|
#include "maparea/cutscenemapareawait.h"
|
||||||
#include "battle/cutscenestartbattle.h"
|
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
@@ -52,7 +51,6 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
|
CUTSCENE_ITEM_TYPE_MAP_AREA_ADD,
|
||||||
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
|
||||||
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
|
||||||
CUTSCENE_ITEM_TYPE_START_BATTLE,
|
|
||||||
CUTSCENE_ITEM_TYPE_EMOJI,
|
CUTSCENE_ITEM_TYPE_EMOJI,
|
||||||
CUTSCENE_ITEM_TYPE_SHAKE,
|
CUTSCENE_ITEM_TYPE_SHAKE,
|
||||||
|
|
||||||
@@ -82,7 +80,6 @@ struct cutsceneitem_s {
|
|||||||
cutscenemapareaadd_t mapAreaAdd;
|
cutscenemapareaadd_t mapAreaAdd;
|
||||||
cutscenemaparearemove_t mapAreaRemove;
|
cutscenemaparearemove_t mapAreaRemove;
|
||||||
cutscenemapareawait_t mapAreaWait;
|
cutscenemapareawait_t mapAreaWait;
|
||||||
cutscenestartbattle_t startBattle;
|
|
||||||
cutsceneemoji_t emoji;
|
cutsceneemoji_t emoji;
|
||||||
cutsceneshake_t shake;
|
cutsceneshake_t shake;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
#include "rpg/cutscene/scene/testcutscene.h"
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/backpack.h"
|
||||||
#include "rpg/battle/party.h"
|
|
||||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "rpgcamera.h"
|
#include "rpgcamera.h"
|
||||||
@@ -34,7 +33,6 @@ errorret_t rpgInit(void) {
|
|||||||
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||||
|
|
||||||
backpackInit();
|
backpackInit();
|
||||||
partyInit();
|
|
||||||
cutsceneSystemInit();
|
cutsceneSystemInit();
|
||||||
|
|
||||||
errorChain(mapInit());
|
errorChain(mapInit());
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# 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
|
|
||||||
storyflag.c
|
|
||||||
)
|
|
||||||
|
|
||||||
# Story Flag Definitions
|
|
||||||
dusk_run_python(
|
|
||||||
dusk_story_defs
|
|
||||||
tools.story
|
|
||||||
--csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv
|
|
||||||
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/story/storyflagvalue.h
|
|
||||||
)
|
|
||||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_story_defs)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "storyflag.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value) {
|
|
||||||
assertTrue(flag > STORY_FLAG_NULL && flag < STORY_FLAG_COUNT, "Bad Flag");
|
|
||||||
STORY_FLAG_VALUES[flag] = value;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
id,description,initial
|
|
||||||
test,"Test flag for debugging purposes",1
|
|
||||||
|
@@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "rpg/story/storyflagvalue.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the value of a story flag.
|
|
||||||
*
|
|
||||||
* @param flag The story flag to get.
|
|
||||||
* @return The value of the story flag.
|
|
||||||
*/
|
|
||||||
#define storyFlagGet(flag) (STORY_FLAG_VALUES[(flag)])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value of a story flag.
|
|
||||||
*
|
|
||||||
* @param flag The story flag to set.
|
|
||||||
* @param value The value to set the story flag to.
|
|
||||||
*/
|
|
||||||
void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
|
|
||||||
typedef uint8_t storyflagvalue_t;
|
|
||||||
@@ -10,5 +10,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(overworld)
|
add_subdirectory(overworld)
|
||||||
add_subdirectory(battle)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
scenebattle.c
|
|
||||||
)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "scenebattle.h"
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
|
|
||||||
errorret_t sceneBattleInit(scenedata_t *sceneData) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t sceneBattleUpdate(scenedata_t *sceneData) {
|
|
||||||
battleUpdate();
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t sceneBattleRender(scenedata_t *sceneData) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t sceneBattleDispose(scenedata_t *sceneData) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "scene/scenebase.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
} scenebattle_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the battle scene. The battle itself (BATTLE global) is
|
|
||||||
* expected to already be started, e.g. by a StartBattle cutscene item.
|
|
||||||
*
|
|
||||||
* @param sceneData The scene data used for this scene.
|
|
||||||
* @return An error if the init failed, or errorOk() if it succeeded.
|
|
||||||
*/
|
|
||||||
errorret_t sceneBattleInit(scenedata_t *sceneData);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the battle scene, ticking the battle simulation.
|
|
||||||
*
|
|
||||||
* @param sceneData The scene data used for this scene.
|
|
||||||
* @return An error if the update failed, or errorOk() if it succeeded.
|
|
||||||
*/
|
|
||||||
errorret_t sceneBattleUpdate(scenedata_t *sceneData);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the battle scene.
|
|
||||||
*
|
|
||||||
* @param sceneData The scene data used for this scene.
|
|
||||||
* @return An error if the render failed, or errorOk() if it succeeded.
|
|
||||||
*/
|
|
||||||
errorret_t sceneBattleRender(scenedata_t *sceneData);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes the battle scene.
|
|
||||||
*
|
|
||||||
* @param sceneData The scene data used for this scene.
|
|
||||||
* @return An error if the dispose failed, or errorOk() if it succeeded.
|
|
||||||
*/
|
|
||||||
errorret_t sceneBattleDispose(scenedata_t *sceneData);
|
|
||||||
@@ -16,12 +16,5 @@ scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = {
|
|||||||
.render = sceneOverworldRender,
|
.render = sceneOverworldRender,
|
||||||
.dispose = sceneOverworldDispose
|
.dispose = sceneOverworldDispose
|
||||||
},
|
},
|
||||||
|
|
||||||
[SCENE_TYPE_BATTLE] = {
|
|
||||||
.init = sceneBattleInit,
|
|
||||||
.update = sceneBattleUpdate,
|
|
||||||
.render = sceneBattleRender,
|
|
||||||
.dispose = sceneBattleDispose
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "scene/scenebase.h"
|
#include "scene/scenebase.h"
|
||||||
#include "scene/overworld/sceneoverworld.h"
|
#include "scene/overworld/sceneoverworld.h"
|
||||||
#include "scene/battle/scenebattle.h"
|
|
||||||
|
|
||||||
typedef union scenedata_u {
|
typedef union scenedata_u {
|
||||||
sceneoverworld_t overworld;
|
sceneoverworld_t overworld;
|
||||||
scenebattle_t battle;
|
|
||||||
} scenedata_t;
|
} scenedata_t;
|
||||||
|
|
||||||
typedef errorret_t (*scenecallback_t)(scenedata_t *);
|
typedef errorret_t (*scenecallback_t)(scenedata_t *);
|
||||||
@@ -28,7 +26,6 @@ typedef enum {
|
|||||||
SCENE_TYPE_NULL,
|
SCENE_TYPE_NULL,
|
||||||
|
|
||||||
SCENE_TYPE_OVERWORLD,
|
SCENE_TYPE_OVERWORLD,
|
||||||
SCENE_TYPE_BATTLE,
|
|
||||||
|
|
||||||
SCENE_TYPE_COUNT
|
SCENE_TYPE_COUNT
|
||||||
} scenetype_t;
|
} scenetype_t;
|
||||||
|
|||||||
@@ -11,5 +11,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
|
|
||||||
add_subdirectory(game)
|
add_subdirectory(game)
|
||||||
add_subdirectory(settings)
|
add_subdirectory(settings)
|
||||||
add_subdirectory(battle)
|
|
||||||
add_subdirectory(backpack)
|
add_subdirectory(backpack)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uibattlemenu.c
|
|
||||||
)
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uibattlemenu.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
|
|
||||||
uibattlemenu_t UI_BATTLE_MENU;
|
|
||||||
|
|
||||||
void uiBattleMenuTargetSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
battlePlayerAttack(UI_BATTLE_MENU.targetFighterIndex[index]);
|
|
||||||
uiMenuClose(&UI_BATTLE_MENU.targetMenu);
|
|
||||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiBattleMenuOpenTargets(void) {
|
|
||||||
battlefighter_t *current = battleGetCurrentFighter();
|
|
||||||
if(current == NULL) return;
|
|
||||||
|
|
||||||
const battlefighterteam_t enemyTeam =
|
|
||||||
current->team == BATTLE_FIGHTER_TEAM_ALLY ?
|
|
||||||
BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY;
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&UI_BATTLE_MENU.targetMenu, UI_BATTLE_MENU.targetItems,
|
|
||||||
uiBattleMenuTargetSelected, NULL, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < BATTLE_FIGHTER_COUNT_MAX; i++) {
|
|
||||||
battlefighter_t *candidate = &BATTLE.fighters[i];
|
|
||||||
if(candidate->team != enemyTeam) continue;
|
|
||||||
if(!battleFighterIsAlive(candidate)) continue;
|
|
||||||
|
|
||||||
stringFormat(
|
|
||||||
UI_BATTLE_MENU.targetLabels[menuIndex],
|
|
||||||
UI_BATTLE_MENU_TARGET_LABEL_MAX - 1,
|
|
||||||
"Enemy %u (%u/%u HP)",
|
|
||||||
candidate->id, candidate->health, candidate->healthMax
|
|
||||||
);
|
|
||||||
UI_BATTLE_MENU.targetFighterIndex[menuIndex] = i;
|
|
||||||
MENU_BUTTON(UI_BATTLE_MENU.targetLabels[menuIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
MENU_END(UI_BATTLE_MENU.targetItems, 1);
|
|
||||||
|
|
||||||
uiMenuOpen(&UI_BATTLE_MENU.targetMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiBattleMenuActionSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
switch(index) {
|
|
||||||
case UI_BATTLE_MENU_ACTION_INDEX_ATTACK:
|
|
||||||
uiBattleMenuOpenTargets();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UI_BATTLE_MENU_ACTION_INDEX_FLEE:
|
|
||||||
battlePlayerFlee();
|
|
||||||
uiMenuClose(&UI_BATTLE_MENU.actionMenu);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiBattleMenuInit(void) {
|
|
||||||
memoryZero(&UI_BATTLE_MENU, sizeof(uibattlemenu_t));
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&UI_BATTLE_MENU.actionMenu, UI_BATTLE_MENU.actionItems,
|
|
||||||
uiBattleMenuActionSelected, NULL, NULL
|
|
||||||
);
|
|
||||||
MENU_BUTTON("Attack");
|
|
||||||
MENU_BUTTON("Flee");
|
|
||||||
MENU_END(UI_BATTLE_MENU.actionItems, 1);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiBattleMenuUpdate(void) {
|
|
||||||
if(uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk();
|
|
||||||
|
|
||||||
battlefighter_t *current = battleGetCurrentFighter();
|
|
||||||
if(current == NULL) errorOk();
|
|
||||||
if(current->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) errorOk();
|
|
||||||
|
|
||||||
uiMenuOpen(&UI_BATTLE_MENU.actionMenu);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiBattleMenuDraw(void) {
|
|
||||||
if(!uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk();
|
|
||||||
|
|
||||||
const float_t width = UI_BATTLE_MENU_WIDTH;
|
|
||||||
const float_t height = UI_BATTLE_MENU_HEIGHT;
|
|
||||||
const float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - width;
|
|
||||||
const float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - height;
|
|
||||||
|
|
||||||
errorChain(uiFrameDraw(x, y, width, height));
|
|
||||||
errorChain(uiMenuDraw(
|
|
||||||
&UI_BATTLE_MENU.actionMenu,
|
|
||||||
x + UI_FRAME_START_X,
|
|
||||||
y + UI_FRAME_START_Y,
|
|
||||||
width - (UI_FRAME_START_X * 2),
|
|
||||||
height - (UI_FRAME_START_Y * 2)
|
|
||||||
));
|
|
||||||
|
|
||||||
if(uiMenuIsActive(&UI_BATTLE_MENU.targetMenu)) {
|
|
||||||
const float_t targetY = y - height;
|
|
||||||
errorChain(uiFrameDraw(x, targetY, width, height));
|
|
||||||
errorChain(uiMenuDraw(
|
|
||||||
&UI_BATTLE_MENU.targetMenu,
|
|
||||||
x + UI_FRAME_START_X,
|
|
||||||
targetY + UI_FRAME_START_Y,
|
|
||||||
width - (UI_FRAME_START_X * 2),
|
|
||||||
height - (UI_FRAME_START_Y * 2)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(spriteBatchFlush());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,57 +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 "ui/widget/uimenu.h"
|
|
||||||
#include "rpg/battle/battle.h"
|
|
||||||
|
|
||||||
#define UI_BATTLE_MENU_ACTION_ITEM_COUNT 2
|
|
||||||
#define UI_BATTLE_MENU_ACTION_INDEX_ATTACK 0
|
|
||||||
#define UI_BATTLE_MENU_ACTION_INDEX_FLEE 1
|
|
||||||
|
|
||||||
#define UI_BATTLE_MENU_TARGET_ITEM_COUNT BATTLE_FIGHTER_COUNT_MAX
|
|
||||||
#define UI_BATTLE_MENU_TARGET_LABEL_MAX 32
|
|
||||||
|
|
||||||
#define UI_BATTLE_MENU_WIDTH 160.0f
|
|
||||||
#define UI_BATTLE_MENU_HEIGHT 96.0f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t actionMenu;
|
|
||||||
uimenuitem_t actionItems[UI_BATTLE_MENU_ACTION_ITEM_COUNT];
|
|
||||||
|
|
||||||
uimenu_t targetMenu;
|
|
||||||
uimenuitem_t targetItems[UI_BATTLE_MENU_TARGET_ITEM_COUNT];
|
|
||||||
uint8_t targetFighterIndex[UI_BATTLE_MENU_TARGET_ITEM_COUNT];
|
|
||||||
char_t targetLabels[UI_BATTLE_MENU_TARGET_ITEM_COUNT]
|
|
||||||
[UI_BATTLE_MENU_TARGET_LABEL_MAX];
|
|
||||||
} uibattlemenu_t;
|
|
||||||
|
|
||||||
extern uibattlemenu_t UI_BATTLE_MENU;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the battle action/target menus.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiBattleMenuInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the battle menu: opens the action menu whenever it becomes a
|
|
||||||
* player-controlled fighter's turn.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiBattleMenuUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the battle action menu, and the target menu above it when
|
|
||||||
* open. No-op when the action menu isn't active.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiBattleMenuDraw(void);
|
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
#include "ui/debug/uiconsole.h"
|
#include "ui/debug/uiconsole.h"
|
||||||
#include "ui/frame/settings/uisettings.h"
|
#include "ui/frame/settings/uisettings.h"
|
||||||
#include "ui/frame/game/uigamemenu.h"
|
#include "ui/frame/game/uigamemenu.h"
|
||||||
#include "ui/frame/battle/uibattlemenu.h"
|
|
||||||
#include "ui/frame/backpack/uibackpack.h"
|
#include "ui/frame/backpack/uibackpack.h"
|
||||||
#include "ui/frame/uiconfirm.h"
|
#include "ui/frame/uiconfirm.h"
|
||||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||||
@@ -53,12 +52,6 @@ uielement_t UI_ELEMENTS[] = {
|
|||||||
.dispose = uiGameMenuDispose
|
.dispose = uiGameMenuDispose
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
.init = uiBattleMenuInit,
|
|
||||||
.update = uiBattleMenuUpdate,
|
|
||||||
.draw = uiBattleMenuDraw
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
.init = uiBackpackInit,
|
.init = uiBackpackInit,
|
||||||
.draw = uiBackpackDraw,
|
.draw = uiBackpackDraw,
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import csv
|
|
||||||
import os
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Story CSV to .h defines")
|
|
||||||
parser.add_argument("--csv", required=True, help="Path to story CSV file")
|
|
||||||
parser.add_argument("--output", required=True, help="Path to output .h file")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
def flag_enum(name):
|
|
||||||
return "STORY_FLAG_" + name.upper().replace(" ", "_")
|
|
||||||
|
|
||||||
# Load flags
|
|
||||||
flags = []
|
|
||||||
with open(args.csv, newline="", encoding="utf-8") as f:
|
|
||||||
reader = csv.DictReader(f)
|
|
||||||
if "id" not in reader.fieldnames:
|
|
||||||
raise ValueError("CSV must have an 'id' column")
|
|
||||||
for row in reader:
|
|
||||||
flags.append({
|
|
||||||
"id": row["id"].strip(),
|
|
||||||
"initial": (row.get("initial") or "0").strip(),
|
|
||||||
})
|
|
||||||
|
|
||||||
# Build output
|
|
||||||
out = [
|
|
||||||
"#pragma once",
|
|
||||||
'#include "rpg/story/storyflagdefs.h"',
|
|
||||||
"",
|
|
||||||
"typedef enum {",
|
|
||||||
" STORY_FLAG_NULL,",
|
|
||||||
"",
|
|
||||||
]
|
|
||||||
for flag in flags:
|
|
||||||
out.append(f" {flag_enum(flag['id'])},")
|
|
||||||
out += [
|
|
||||||
"",
|
|
||||||
" STORY_FLAG_COUNT",
|
|
||||||
"} storyflag_t;",
|
|
||||||
"",
|
|
||||||
"static storyflagvalue_t STORY_FLAG_VALUES[STORY_FLAG_COUNT] = {",
|
|
||||||
]
|
|
||||||
for flag in flags:
|
|
||||||
out.append(f" [{flag_enum(flag['id'])}] = {flag['initial']},")
|
|
||||||
out += [
|
|
||||||
"};",
|
|
||||||
"",
|
|
||||||
"static const char_t *STORY_FLAG_SCRIPT =",
|
|
||||||
]
|
|
||||||
for i, flag in enumerate(flags):
|
|
||||||
out.append(f" \"{flag_enum(flag['id'])} = {i + 1}\\n\"")
|
|
||||||
out += [";", ""]
|
|
||||||
|
|
||||||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
|
||||||
with open(args.output, "w", encoding="utf-8") as f:
|
|
||||||
f.write("\n".join(out))
|
|
||||||
Reference in New Issue
Block a user