Remove story and battle stuff
This commit is contained in:
@@ -11,10 +11,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(battle)
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(overworld)
|
||||
|
||||
add_subdirectory(story)
|
||||
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(maparea)
|
||||
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
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
|
||||
.init = cutsceneStartBattleStart,
|
||||
.update = cutsceneStartBattleUpdate
|
||||
},
|
||||
|
||||
[CUTSCENE_ITEM_TYPE_EMOJI] = {
|
||||
.init = cutsceneEmojiStart,
|
||||
.update = cutsceneEmojiUpdate
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "maparea/cutscenemapareaadd.h"
|
||||
#include "maparea/cutscenemaparearemove.h"
|
||||
#include "maparea/cutscenemapareawait.h"
|
||||
#include "battle/cutscenestartbattle.h"
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
@@ -52,7 +51,6 @@ typedef enum {
|
||||
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,
|
||||
|
||||
@@ -82,7 +80,6 @@ struct cutsceneitem_s {
|
||||
cutscenemapareaadd_t mapAreaAdd;
|
||||
cutscenemaparearemove_t mapAreaRemove;
|
||||
cutscenemapareawait_t mapAreaWait;
|
||||
cutscenestartbattle_t startBattle;
|
||||
cutsceneemoji_t emoji;
|
||||
cutsceneshake_t shake;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "rpg/cutscene/scene/testcutscene.h"
|
||||
#include "rpg/item/backpack.h"
|
||||
#include "rpg/battle/party.h"
|
||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||
#include "time/time.h"
|
||||
#include "rpgcamera.h"
|
||||
@@ -34,7 +33,6 @@ errorret_t rpgInit(void) {
|
||||
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||
|
||||
backpackInit();
|
||||
partyInit();
|
||||
cutsceneSystemInit();
|
||||
|
||||
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
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(battle)
|
||||
add_subdirectory(overworld)
|
||||
@@ -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,
|
||||
.dispose = sceneOverworldDispose
|
||||
},
|
||||
|
||||
[SCENE_TYPE_BATTLE] = {
|
||||
.init = sceneBattleInit,
|
||||
.update = sceneBattleUpdate,
|
||||
.render = sceneBattleRender,
|
||||
.dispose = sceneBattleDispose
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,11 +8,9 @@
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
#include "scene/overworld/sceneoverworld.h"
|
||||
#include "scene/battle/scenebattle.h"
|
||||
|
||||
typedef union scenedata_u {
|
||||
sceneoverworld_t overworld;
|
||||
scenebattle_t battle;
|
||||
} scenedata_t;
|
||||
|
||||
typedef errorret_t (*scenecallback_t)(scenedata_t *);
|
||||
@@ -28,7 +26,6 @@ typedef enum {
|
||||
SCENE_TYPE_NULL,
|
||||
|
||||
SCENE_TYPE_OVERWORLD,
|
||||
SCENE_TYPE_BATTLE,
|
||||
|
||||
SCENE_TYPE_COUNT
|
||||
} scenetype_t;
|
||||
|
||||
@@ -11,5 +11,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
|
||||
add_subdirectory(game)
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(battle)
|
||||
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/frame/settings/uisettings.h"
|
||||
#include "ui/frame/game/uigamemenu.h"
|
||||
#include "ui/frame/battle/uibattlemenu.h"
|
||||
#include "ui/frame/backpack/uibackpack.h"
|
||||
#include "ui/frame/uiconfirm.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
@@ -53,12 +52,6 @@ uielement_t UI_ELEMENTS[] = {
|
||||
.dispose = uiGameMenuDispose
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiBattleMenuInit,
|
||||
.update = uiBattleMenuUpdate,
|
||||
.draw = uiBattleMenuDraw
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiBackpackInit,
|
||||
.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