Persist party across saves and flag/warn on corrupt save slots
Party moves off a global singleton onto each save slot, letting START_BATTLE pick specific active-order members to bring into a fight, and round-trips through save/load via saveslotver1. A corrupt slot load now flags the cache and shows a red "Corrupted Save" warning in the select-save UI instead of silently resetting. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "START_BATTLE",
|
||||||
|
"encounterType": "REGULAR",
|
||||||
|
"fleeAvailable": true,
|
||||||
|
"enemies": [
|
||||||
|
{
|
||||||
|
"stats": { "attack": 8, "defense": 4, "magic": 0, "speed": 5, "luck": 2 },
|
||||||
|
"healthMax": 30,
|
||||||
|
"mpMax": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"stats": { "attack": 6, "defense": 3, "magic": 0, "speed": 7, "luck": 2 },
|
||||||
|
"healthMax": 20,
|
||||||
|
"mpMax": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REGULAR_BATTLE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -73,6 +73,12 @@ msgstr "Select Save"
|
|||||||
msgid "ui.select_save.empty"
|
msgid "ui.select_save.empty"
|
||||||
msgstr "Empty Slot"
|
msgstr "Empty Slot"
|
||||||
|
|
||||||
|
msgid "ui.select_save.corrupt"
|
||||||
|
msgstr "Corrupted Save"
|
||||||
|
|
||||||
|
msgid "ui.select_save.corrupt_warning"
|
||||||
|
msgstr "This save file is corrupted and could not be loaded. Please contact support if this issue persists."
|
||||||
|
|
||||||
msgid "ui.select_save.slot_format"
|
msgid "ui.select_save.slot_format"
|
||||||
msgstr "%s Lv.%d %s"
|
msgstr "%s Lv.%d %s"
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[6,0],[2,0],[2,0],[2,0],[2,0],[2,0],[2,0],[7,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[4,0],[12,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[9,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[{"file":"house_5_3.dmf","pos":[1,1,0]}],"entities":[{"type":"global","globalId":3,"pos":[8,8,1]},{"type":"item","itemId":1,"quantity":1,"pos":[12,2,0]}],"areas":[{"min":[11,3,0],"max":[16,9,10],"callbackId":1,"notify":3,"trigger":6}]}
|
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[6,0],[2,0],[2,0],[2,0],[2,0],[2,0],[2,0],[7,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[4,0],[12,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[9,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[{"file":"house_5_3.dmf","pos":[1,1,0]}],"entities":[{"type":"global","globalId":3,"pos":[8,8,1]},{"type":"global","globalId":4,"pos":[12,2,0]}],"areas":[{"min":[11,3,0],"max":[16,9,10],"callbackId":1,"notify":3,"trigger":6}]}
|
||||||
+25
-16
@@ -9,63 +9,72 @@
|
|||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
|
||||||
party_t PARTY;
|
void partyInit(party_t *party) {
|
||||||
|
assertNotNull(party, "Party cannot be null");
|
||||||
|
|
||||||
void partyInit(void) {
|
memoryZero(party, sizeof(party_t));
|
||||||
memoryZero(&PARTY, sizeof(party_t));
|
|
||||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
PARTY.members[i].id = i;
|
party->members[i].id = i;
|
||||||
}
|
}
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
PARTY.order[i] = PARTY_ORDER_EMPTY;
|
party->order[i] = PARTY_ORDER_EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t partyGetAvailableMember(void) {
|
uint8_t partyGetAvailableMember(const party_t *party) {
|
||||||
|
assertNotNull(party, "Party cannot be null");
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
if(PARTY.members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
if(party->members[i].status == BATTLE_FIGHTER_STATUS_NULL) return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
battlefighter_t *partyAddMember(
|
battlefighter_t *partyAddMember(
|
||||||
|
party_t *party,
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats,
|
||||||
const uint16_t healthMax,
|
const uint16_t healthMax,
|
||||||
const uint16_t mpMax
|
const uint16_t mpMax
|
||||||
) {
|
) {
|
||||||
const uint8_t index = partyGetAvailableMember();
|
assertNotNull(party, "Party cannot be null");
|
||||||
|
|
||||||
|
const uint8_t index = partyGetAvailableMember(party);
|
||||||
if(index == 0xFF) return NULL;
|
if(index == 0xFF) return NULL;
|
||||||
|
|
||||||
battlefighter_t *member = &PARTY.members[index];
|
battlefighter_t *member = &party->members[index];
|
||||||
battleFighterInit(
|
battleFighterInit(
|
||||||
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
member, BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER,
|
||||||
stats, healthMax, mpMax
|
stats, healthMax, mpMax
|
||||||
);
|
);
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
if(PARTY.order[i] != PARTY_ORDER_EMPTY) continue;
|
if(party->order[i] != PARTY_ORDER_EMPTY) continue;
|
||||||
PARTY.order[i] = index;
|
party->order[i] = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
battlefighter_t *partyGetOrderMember(const uint8_t slot) {
|
battlefighter_t *partyGetOrderMember(party_t *party, const uint8_t slot) {
|
||||||
|
assertNotNull(party, "Party cannot be null");
|
||||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
|
|
||||||
const uint8_t index = PARTY.order[slot];
|
const uint8_t index = party->order[slot];
|
||||||
if(index == PARTY_ORDER_EMPTY) return NULL;
|
if(index == PARTY_ORDER_EMPTY) return NULL;
|
||||||
return &PARTY.members[index];
|
return &party->members[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex) {
|
void partySetOrder(
|
||||||
|
party_t *party, const uint8_t slot, const uint8_t memberIndex
|
||||||
|
) {
|
||||||
|
assertNotNull(party, "Party cannot be null");
|
||||||
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot");
|
||||||
assertTrue(
|
assertTrue(
|
||||||
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX,
|
||||||
"Invalid party member index"
|
"Invalid party member index"
|
||||||
);
|
);
|
||||||
|
|
||||||
PARTY.order[slot] = memberIndex;
|
party->order[slot] = memberIndex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,25 +22,27 @@ typedef struct {
|
|||||||
uint8_t order[PARTY_ACTIVE_SIZE_MAX];
|
uint8_t order[PARTY_ACTIVE_SIZE_MAX];
|
||||||
} party_t;
|
} party_t;
|
||||||
|
|
||||||
extern party_t PARTY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the party system with an empty roster and order.
|
* Initializes the party with an empty roster and order.
|
||||||
|
*
|
||||||
|
* @param party The party to initialize.
|
||||||
*/
|
*/
|
||||||
void partyInit(void);
|
void partyInit(party_t *party);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets an available (unused) party member slot index.
|
* Gets an available (unused) party member slot index.
|
||||||
*
|
*
|
||||||
|
* @param party The party to query.
|
||||||
* @return The index of an available slot, or 0xFF if the party is full.
|
* @return The index of an available slot, or 0xFF if the party is full.
|
||||||
*/
|
*/
|
||||||
uint8_t partyGetAvailableMember(void);
|
uint8_t partyGetAvailableMember(const party_t *party);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a member to the party roster in the next available slot. Party
|
* 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
|
* members are always allies controlled by the player. If there is a
|
||||||
* free active order slot, the new member is placed into it.
|
* free active order slot, the new member is placed into it.
|
||||||
*
|
*
|
||||||
|
* @param party The party to add the member to.
|
||||||
* @param stats The member's base combat stats.
|
* @param stats The member's base combat stats.
|
||||||
* @param healthMax The member's maximum health.
|
* @param healthMax The member's maximum health.
|
||||||
* @param mpMax The member's maximum mp.
|
* @param mpMax The member's maximum mp.
|
||||||
@@ -48,6 +50,7 @@ uint8_t partyGetAvailableMember(void);
|
|||||||
* already full.
|
* already full.
|
||||||
*/
|
*/
|
||||||
battlefighter_t *partyAddMember(
|
battlefighter_t *partyAddMember(
|
||||||
|
party_t *party,
|
||||||
const battlefighterstats_t stats,
|
const battlefighterstats_t stats,
|
||||||
const uint16_t healthMax,
|
const uint16_t healthMax,
|
||||||
const uint16_t mpMax
|
const uint16_t mpMax
|
||||||
@@ -56,18 +59,22 @@ battlefighter_t *partyAddMember(
|
|||||||
/**
|
/**
|
||||||
* Gets the roster member currently occupying an active order slot.
|
* Gets the roster member currently occupying an active order slot.
|
||||||
*
|
*
|
||||||
|
* @param party The party to query.
|
||||||
* @param slot The active order slot to query.
|
* @param slot The active order slot to query.
|
||||||
* @return Pointer to the member in that slot, or NULL if the slot is
|
* @return Pointer to the member in that slot, or NULL if the slot is
|
||||||
* empty.
|
* empty.
|
||||||
*/
|
*/
|
||||||
battlefighter_t *partyGetOrderMember(const uint8_t slot);
|
battlefighter_t *partyGetOrderMember(party_t *party, const uint8_t slot);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assigns a roster member to an active order slot, replacing whatever
|
* Assigns a roster member to an active order slot, replacing whatever
|
||||||
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
* was there. Use PARTY_ORDER_EMPTY to clear a slot.
|
||||||
*
|
*
|
||||||
|
* @param party The party to modify.
|
||||||
* @param slot The active order slot to assign.
|
* @param slot The active order slot to assign.
|
||||||
* @param memberIndex The roster member index to place there, or
|
* @param memberIndex The roster member index to place there, or
|
||||||
* PARTY_ORDER_EMPTY to clear the slot.
|
* PARTY_ORDER_EMPTY to clear the slot.
|
||||||
*/
|
*/
|
||||||
void partySetOrder(const uint8_t slot, const uint8_t memberIndex);
|
void partySetOrder(
|
||||||
|
party_t *party, const uint8_t slot, const uint8_t memberIndex
|
||||||
|
);
|
||||||
|
|||||||
@@ -8,4 +8,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
cutscenestartbattle.c
|
cutscenestartbattle.c
|
||||||
cutscenebattlewaitstate.c
|
cutscenebattlewaitstate.c
|
||||||
cutscenebattleforceaction.c
|
cutscenebattleforceaction.c
|
||||||
|
cutsceneregularbattle.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
|
||||||
|
bool_t cutsceneRegularBattleUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a regular-battle step. Currently a placeholder - behaves exactly
|
||||||
|
* like IDLE (see cutsceneidle.h) and never completes on its own.
|
||||||
|
*
|
||||||
|
* @param item The cutscene item.
|
||||||
|
* @param data Runtime data storage.
|
||||||
|
* @returns false always.
|
||||||
|
*/
|
||||||
|
bool_t cutsceneRegularBattleUpdate(
|
||||||
|
const cutsceneitem_t *item,
|
||||||
|
cutsceneitemdata_t *data
|
||||||
|
);
|
||||||
|
|
||||||
|
// REGULAR_BATTLE has no fields of its own to parse yet - see
|
||||||
|
// cutsceneItemLoadNoop (cutsceneitembase.h), used directly as its .load
|
||||||
|
// callback.
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
#include "rpg/battle/party.h"
|
#include "rpg/battle/party.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
|
#include "save/save.h"
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonbattleencounter.h"
|
#include "rpg/cutscene/item/json/cutscenejsonbattleencounter.h"
|
||||||
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
#include "rpg/cutscene/item/json/cutscenejsonfield.h"
|
||||||
|
|
||||||
@@ -19,8 +20,9 @@ void cutsceneStartBattleStart(
|
|||||||
|
|
||||||
battleInit();
|
battleInit();
|
||||||
|
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
for(uint8_t i = 0; i < config->playerCount; i++) {
|
||||||
battlefighter_t *member = partyGetOrderMember(i);
|
battlefighter_t *member =
|
||||||
|
partyGetOrderMember(&SAVE.slot.party, config->players[i]);
|
||||||
if(member == NULL) continue;
|
if(member == NULL) continue;
|
||||||
|
|
||||||
battlefighter_t *fighter = battleAddFighter(
|
battlefighter_t *fighter = battleAddFighter(
|
||||||
@@ -52,13 +54,15 @@ bool_t cutsceneStartBattleUpdate(
|
|||||||
) {
|
) {
|
||||||
if(BATTLE.result == BATTLE_RESULT_NONE) return false;
|
if(BATTLE.result == BATTLE_RESULT_NONE) return false;
|
||||||
|
|
||||||
|
const cutscenestartbattle_t *config = &item->startBattle;
|
||||||
|
|
||||||
// Sync ally HP/MP back to the persistent party roster. Relies on
|
// Sync ally HP/MP back to the persistent party roster. Relies on
|
||||||
// ally fighters having been added to BATTLE.fighters in the same
|
// ally fighters having been added to BATTLE.fighters in the same
|
||||||
// order partyGetOrderMember() iterates, starting at index 0 (see
|
// order config->players iterates (see cutsceneStartBattleStart).
|
||||||
// cutsceneStartBattleStart).
|
|
||||||
uint8_t allySlot = 0;
|
uint8_t allySlot = 0;
|
||||||
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
for(uint8_t i = 0; i < config->playerCount; i++) {
|
||||||
battlefighter_t *member = partyGetOrderMember(i);
|
battlefighter_t *member =
|
||||||
|
partyGetOrderMember(&SAVE.slot.party, config->players[i]);
|
||||||
if(member == NULL) continue;
|
if(member == NULL) continue;
|
||||||
|
|
||||||
battlefighter_t *fighter = &BATTLE.fighters[allySlot++];
|
battlefighter_t *fighter = &BATTLE.fighters[allySlot++];
|
||||||
@@ -89,6 +93,38 @@ errorret_t cutsceneStartBattleLoad(
|
|||||||
item->startBattle.fleeAvailable =
|
item->startBattle.fleeAvailable =
|
||||||
cutsceneJsonGetBool(itemObj, "fleeAvailable", true);
|
cutsceneJsonGetBool(itemObj, "fleeAvailable", true);
|
||||||
|
|
||||||
|
yyjson_val *players = yyjson_obj_get(itemObj, "players");
|
||||||
|
if(players == NULL) {
|
||||||
|
item->startBattle.playerCount = PARTY_ACTIVE_SIZE_MAX;
|
||||||
|
for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) {
|
||||||
|
item->startBattle.players[i] = i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size_t playerCount = yyjson_arr_size(players);
|
||||||
|
if(!yyjson_is_arr(players) || playerCount > PARTY_ACTIVE_SIZE_MAX) {
|
||||||
|
errorThrow(
|
||||||
|
"Cutscene item (START_BATTLE): 'players' must have 0-%u entries",
|
||||||
|
(uint32_t)PARTY_ACTIVE_SIZE_MAX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
item->startBattle.playerCount = (uint8_t)playerCount;
|
||||||
|
|
||||||
|
yyjson_arr_iter playerIter = yyjson_arr_iter_with(players);
|
||||||
|
yyjson_val *playerVal;
|
||||||
|
size_t p = 0;
|
||||||
|
while((playerVal = yyjson_arr_iter_next(&playerIter)) != NULL) {
|
||||||
|
int64_t index = yyjson_get_int(playerVal);
|
||||||
|
if(index < 0 || index >= PARTY_ACTIVE_SIZE_MAX) {
|
||||||
|
errorThrow(
|
||||||
|
"Cutscene item (START_BATTLE): players[%u] must be 0-%u",
|
||||||
|
(uint32_t)p, (uint32_t)(PARTY_ACTIVE_SIZE_MAX - 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
item->startBattle.players[p] = (uint8_t)index;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies");
|
yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies");
|
||||||
size_t enemyCount = yyjson_arr_size(enemies);
|
size_t enemyCount = yyjson_arr_size(enemies);
|
||||||
if(!yyjson_is_arr(enemies) || enemyCount == 0 ||
|
if(!yyjson_is_arr(enemies) || enemyCount == 0 ||
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/battle/battle.h"
|
#include "rpg/battle/battle.h"
|
||||||
|
#include "rpg/battle/party.h"
|
||||||
#include "rpg/cutscene/item/cutsceneitembase.h"
|
#include "rpg/cutscene/item/cutsceneitembase.h"
|
||||||
|
|
||||||
#define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4
|
#define CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX 4
|
||||||
@@ -22,6 +23,13 @@ typedef struct {
|
|||||||
bool_t fleeAvailable;
|
bool_t fleeAvailable;
|
||||||
uint8_t enemyCount;
|
uint8_t enemyCount;
|
||||||
cutscenestartbattleenemy_t enemies[CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX];
|
cutscenestartbattleenemy_t enemies[CUTSCENE_START_BATTLE_ENEMY_COUNT_MAX];
|
||||||
|
|
||||||
|
// Active party order slots (see party.h) to bring into the battle, in
|
||||||
|
// the order they should be added. Defaults to every active slot, 0
|
||||||
|
// through PARTY_ACTIVE_SIZE_MAX - 1 in order, when the item's JSON
|
||||||
|
// omits "players" - see cutsceneStartBattleLoad.
|
||||||
|
uint8_t players[PARTY_ACTIVE_SIZE_MAX];
|
||||||
|
uint8_t playerCount;
|
||||||
} cutscenestartbattle_t;
|
} cutscenestartbattle_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -327,6 +327,12 @@ cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
|
|||||||
.update = cutsceneUIShowUpdate,
|
.update = cutsceneUIShowUpdate,
|
||||||
.jsonName = "UI_SHOW",
|
.jsonName = "UI_SHOW",
|
||||||
.load = cutsceneUIShowLoad
|
.load = cutsceneUIShowLoad
|
||||||
|
},
|
||||||
|
|
||||||
|
[CUTSCENE_ITEM_TYPE_REGULAR_BATTLE] = {
|
||||||
|
.update = cutsceneRegularBattleUpdate,
|
||||||
|
.jsonName = "REGULAR_BATTLE",
|
||||||
|
.load = cutsceneItemLoadNoop
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
#include "battle/cutscenestartbattle.h"
|
#include "battle/cutscenestartbattle.h"
|
||||||
#include "battle/cutscenebattlewaitstate.h"
|
#include "battle/cutscenebattlewaitstate.h"
|
||||||
#include "battle/cutscenebattleforceaction.h"
|
#include "battle/cutscenebattleforceaction.h"
|
||||||
|
#include "battle/cutsceneregularbattle.h"
|
||||||
#include "save/cutscenesavedevicecheck.h"
|
#include "save/cutscenesavedevicecheck.h"
|
||||||
#include "save/cutscenesaveloadallslots.h"
|
#include "save/cutscenesaveloadallslots.h"
|
||||||
#include "audio/cutsceneaudioplay.h"
|
#include "audio/cutsceneaudioplay.h"
|
||||||
@@ -118,6 +119,7 @@ typedef enum {
|
|||||||
CUTSCENE_ITEM_TYPE_AUDIO_SET,
|
CUTSCENE_ITEM_TYPE_AUDIO_SET,
|
||||||
CUTSCENE_ITEM_TYPE_IDLE,
|
CUTSCENE_ITEM_TYPE_IDLE,
|
||||||
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
CUTSCENE_ITEM_TYPE_UI_SHOW,
|
||||||
|
CUTSCENE_ITEM_TYPE_REGULAR_BATTLE,
|
||||||
|
|
||||||
CUTSCENE_ITEM_TYPE_COUNT
|
CUTSCENE_ITEM_TYPE_COUNT
|
||||||
} cutsceneitemtype_t;
|
} cutsceneitemtype_t;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "entityglobaldefs.h"
|
#include "entityglobaldefs.h"
|
||||||
#include "rpg/cutscene/scene/testcutscene.h"
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
|
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||||
|
|
||||||
ENTITY_GLOBAL_CALLBACK(3) {
|
ENTITY_GLOBAL_CALLBACK(3) {
|
||||||
create->entity->data.npc.moveType = NPC_MOVE_TYPE_PATH;
|
create->entity->data.npc.moveType = NPC_MOVE_TYPE_PATH;
|
||||||
@@ -20,11 +21,17 @@ ENTITY_GLOBAL_CALLBACK(3) {
|
|||||||
create->entity->interact.data.cutscene = CUTSCENE_REFERENCE(TEST_TWO);
|
create->entity->interact.data.cutscene = CUTSCENE_REFERENCE(TEST_TWO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENTITY_GLOBAL_CALLBACK(4) {
|
||||||
|
create->entity->interact.type = ENTITY_INTERACT_CUTSCENE;
|
||||||
|
create->entity->interact.data.cutscene = cutsceneCutsceneResolve("battletest");
|
||||||
|
}
|
||||||
|
|
||||||
static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = {
|
static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = {
|
||||||
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_NULL, ENTITY_TYPE_NULL, NULL),
|
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_NULL, ENTITY_TYPE_NULL, NULL),
|
||||||
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_PLAYER, ENTITY_TYPE_PLAYER, NULL),
|
ENTITY_GLOBAL(ENTITY_GLOBAL_ID_PLAYER, ENTITY_TYPE_PLAYER, NULL),
|
||||||
|
|
||||||
ENTITY_GLOBAL(3, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(3)),
|
ENTITY_GLOBAL(3, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(3)),
|
||||||
|
ENTITY_GLOBAL(4, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(4)),
|
||||||
};
|
};
|
||||||
|
|
||||||
//EOF
|
//EOF
|
||||||
|
|||||||
+2
-1
@@ -12,6 +12,7 @@
|
|||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/backpack.h"
|
||||||
#include "rpg/battle/party.h"
|
#include "rpg/battle/party.h"
|
||||||
|
#include "save/save.h"
|
||||||
#include "ui/rpg/textbox/uitextboxminilist.h"
|
#include "ui/rpg/textbox/uitextboxminilist.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "rpgcamera.h"
|
#include "rpgcamera.h"
|
||||||
@@ -27,7 +28,7 @@ errorret_t rpgInit(void) {
|
|||||||
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||||
|
|
||||||
backpackInit();
|
backpackInit();
|
||||||
partyInit();
|
partyInit(&SAVE.slot.party);
|
||||||
cutsceneSystemInit();
|
cutsceneSystemInit();
|
||||||
errorChain(mapInit());
|
errorChain(mapInit());
|
||||||
rpgCameraInit();
|
rpgCameraInit();
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ errorret_t saveLoadSlot() {
|
|||||||
(uint32_t)SAVE.slotCurrent
|
(uint32_t)SAVE.slotCurrent
|
||||||
);
|
);
|
||||||
saveSlotInit(&SAVE.slot);
|
saveSlotInit(&SAVE.slot);
|
||||||
|
SAVE.slot.cachedData.corrupt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -353,6 +353,119 @@
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches an already-built mutable value (e.g. a yyjson_mut_arr() of
|
||||||
|
* per-element objects the caller populated by hand) to the current JSON
|
||||||
|
* object under the given key.
|
||||||
|
*
|
||||||
|
* @param key The key to write to.
|
||||||
|
* @param value The yyjson_mut_val* to attach.
|
||||||
|
*/
|
||||||
|
#define writeValue(key, value) yyjson_mut_obj_add_val(doc, object, key, value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a new JSON object to the given mutable array as one element and
|
||||||
|
* declares `object` in the calling scope pointing at it, shadowing any
|
||||||
|
* outer `object` - so the writeX macros above, once called inside the loop
|
||||||
|
* that builds `arr`, populate this element instead of the outer object.
|
||||||
|
*
|
||||||
|
* @param arr The yyjson_mut_val* array (from yyjson_mut_arr()) to append to.
|
||||||
|
*/
|
||||||
|
#define writeArrayObject(arr) \
|
||||||
|
yyjson_mut_val *object = yyjson_mut_arr_add_obj(doc, arr)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires a key on the current JSON object to be a JSON array of exactly
|
||||||
|
* the given size, throwing otherwise. Declares `dest` (a yyjson_val*) in
|
||||||
|
* the calling scope pointing at the array, ready for the caller to iterate
|
||||||
|
* (e.g. with yyjson_arr_foreach) - unlike the readXArray macros, this
|
||||||
|
* doesn't copy the elements out itself, since each element here is a
|
||||||
|
* nested object the caller parses by hand.
|
||||||
|
*
|
||||||
|
* @param key The key to read from.
|
||||||
|
* @param dest The yyjson_val* variable to declare, pointing at the array.
|
||||||
|
* @param count The exact number of elements the array must have.
|
||||||
|
*/
|
||||||
|
#define requireArrayOfSize(key, dest, count) \
|
||||||
|
yyjson_val *dest = yyjson_obj_get(object, key); \
|
||||||
|
if((dest) == NULL) errorThrow("Save JSON missing '%s' key", key); \
|
||||||
|
if(yyjson_arr_size(dest) != (size_t)(count)) { \
|
||||||
|
errorThrow( \
|
||||||
|
"Save JSON array '%s' has wrong length, expected %d", \
|
||||||
|
key, (int)(count) \
|
||||||
|
); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given key exists on the current JSON object.
|
||||||
|
*
|
||||||
|
* @param key The key to check for.
|
||||||
|
*/
|
||||||
|
#define hasUInt8Array(key) (yyjson_obj_get(object, key) != NULL)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Errors if the given key does not exist on the current JSON object.
|
||||||
|
*
|
||||||
|
* @param key The key that must exist.
|
||||||
|
*/
|
||||||
|
#define requireUInt8Array(key) \
|
||||||
|
if(!hasUInt8Array(key)) errorThrow("Save JSON missing '%s' key", key)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a uint8_t array to the current JSON object.
|
||||||
|
*
|
||||||
|
* @param key The key to write to.
|
||||||
|
* @param value The array to write.
|
||||||
|
* @param count Number of elements in the array.
|
||||||
|
*/
|
||||||
|
#define writeUInt8Array(key, value, count) { \
|
||||||
|
yyjson_mut_val *saveJsonArr = yyjson_mut_arr(doc); \
|
||||||
|
size_t saveJsonArrIdx = 0; \
|
||||||
|
while(saveJsonArrIdx < (size_t)(count)) { \
|
||||||
|
yyjson_mut_arr_add_uint( \
|
||||||
|
doc, saveJsonArr, (uint64_t)(value)[saveJsonArrIdx] \
|
||||||
|
); \
|
||||||
|
saveJsonArrIdx++; \
|
||||||
|
} \
|
||||||
|
yyjson_mut_obj_add_val(doc, object, key, saveJsonArr); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a uint8_t array from the current JSON object into dest, falling
|
||||||
|
* back to filling every element with def if the key is missing. Errors if
|
||||||
|
* the JSON array's length doesn't exactly match count.
|
||||||
|
*
|
||||||
|
* @param key The key to read from.
|
||||||
|
* @param dest The destination array to assign to.
|
||||||
|
* @param count Number of elements dest holds.
|
||||||
|
* @param def The default value to fill every element with if the key is
|
||||||
|
* missing.
|
||||||
|
*/
|
||||||
|
#define readUInt8Array(key, dest, count, def) { \
|
||||||
|
yyjson_val *saveJsonArr = yyjson_obj_get(object, key); \
|
||||||
|
if(saveJsonArr != NULL) { \
|
||||||
|
if(yyjson_arr_size(saveJsonArr) != (size_t)(count)) { \
|
||||||
|
errorThrow( \
|
||||||
|
"Save JSON array '%s' has wrong length, expected %d", \
|
||||||
|
key, (int)(count) \
|
||||||
|
); \
|
||||||
|
} \
|
||||||
|
size_t saveJsonArrIdx, saveJsonArrLen; \
|
||||||
|
yyjson_val *saveJsonArrElem; \
|
||||||
|
yyjson_arr_foreach( \
|
||||||
|
saveJsonArr, saveJsonArrIdx, saveJsonArrLen, saveJsonArrElem \
|
||||||
|
) { \
|
||||||
|
(dest)[saveJsonArrIdx] = (uint8_t)yyjson_get_uint(saveJsonArrElem); \
|
||||||
|
} \
|
||||||
|
} else { \
|
||||||
|
size_t saveJsonArrIdx = 0; \
|
||||||
|
while(saveJsonArrIdx < (size_t)(count)) { \
|
||||||
|
(dest)[saveJsonArrIdx] = (uint8_t)(def); \
|
||||||
|
saveJsonArrIdx++; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given key exists on the current JSON object.
|
* Checks if the given key exists on the current JSON object.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ void saveSlotInit(saveslot_t *slot) {
|
|||||||
assertNotNull(slot, "Slot cannot be null");
|
assertNotNull(slot, "Slot cannot be null");
|
||||||
|
|
||||||
memorySet(slot, 0, sizeof(saveslot_t));
|
memorySet(slot, 0, sizeof(saveslot_t));
|
||||||
|
|
||||||
slot->version = 1;
|
slot->version = 1;
|
||||||
|
|
||||||
|
partyInit(&slot->party);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
bool_t saveSlotInUse(saveslotcache_t *slot) {
|
||||||
@@ -37,6 +40,8 @@ errorret_t saveSlotWriteJSON(
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object) {
|
errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object) {
|
||||||
|
saveSlotInit(slot);
|
||||||
|
|
||||||
errorChain(saveSlotCurrentReadJSON(slot, object));
|
errorChain(saveSlotCurrentReadJSON(slot, object));
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "save/savedevice.h"
|
#include "save/savedevice.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
|
#include "rpg/battle/party.h"
|
||||||
#include "time/timeepoch.h"
|
#include "time/timeepoch.h"
|
||||||
#include "yyjson.h"
|
#include "yyjson.h"
|
||||||
|
|
||||||
@@ -28,6 +29,11 @@ typedef struct {
|
|||||||
dusktimeepoch_t time;
|
dusktimeepoch_t time;
|
||||||
int32_t playerLevel;
|
int32_t playerLevel;
|
||||||
char_t mapName[MAP_NAME_MAX];
|
char_t mapName[MAP_NAME_MAX];
|
||||||
|
|
||||||
|
// Set when this slot's on-disk file failed to load (corrupt or an
|
||||||
|
// incompatible schema version) and was reset to an empty slot instead -
|
||||||
|
// see saveLoadSlot() (save/save.c).
|
||||||
|
bool_t corrupt;
|
||||||
} saveslotcache_t;
|
} saveslotcache_t;
|
||||||
|
|
||||||
typedef struct saveslot_s {
|
typedef struct saveslot_s {
|
||||||
@@ -35,6 +41,7 @@ typedef struct saveslot_s {
|
|||||||
uint8_t dataType;
|
uint8_t dataType;
|
||||||
|
|
||||||
saveslotcache_t cachedData;
|
saveslotcache_t cachedData;
|
||||||
|
party_t party;
|
||||||
} saveslot_t;
|
} saveslot_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "saveslotcurrent.h"
|
#include "saveslotcurrent.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
#include "util/memory.h"
|
||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
|
|
||||||
errorret_t saveSlotCurrentWriteJSON(
|
errorret_t saveSlotCurrentWriteJSON(
|
||||||
@@ -29,6 +30,11 @@ errorret_t saveSlotCurrentWriteJSON(
|
|||||||
ver1.playerLevel = slot->cachedData.playerLevel;
|
ver1.playerLevel = slot->cachedData.playerLevel;
|
||||||
stringCopy(ver1.mapName, slot->cachedData.mapName, sizeof(ver1.mapName));
|
stringCopy(ver1.mapName, slot->cachedData.mapName, sizeof(ver1.mapName));
|
||||||
|
|
||||||
|
memoryCopy(
|
||||||
|
ver1.partyMembers, slot->party.members, sizeof(ver1.partyMembers)
|
||||||
|
);
|
||||||
|
memoryCopy(ver1.partyOrder, slot->party.order, sizeof(ver1.partyOrder));
|
||||||
|
|
||||||
errorChain(saveSlotVer1WriteJSON(&ver1, doc, object));
|
errorChain(saveSlotVer1WriteJSON(&ver1, doc, object));
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -47,5 +53,10 @@ errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object) {
|
|||||||
slot->cachedData.mapName, ver1.mapName, sizeof(slot->cachedData.mapName)
|
slot->cachedData.mapName, ver1.mapName, sizeof(slot->cachedData.mapName)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
memoryCopy(
|
||||||
|
slot->party.members, ver1.partyMembers, sizeof(slot->party.members)
|
||||||
|
);
|
||||||
|
memoryCopy(slot->party.order, ver1.partyOrder, sizeof(slot->party.order));
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,28 @@ errorret_t saveSlotVer1WriteJSON(
|
|||||||
writeInt32("playerLevel", slot->playerLevel);
|
writeInt32("playerLevel", slot->playerLevel);
|
||||||
writeString("mapName", slot->mapName);
|
writeString("mapName", slot->mapName);
|
||||||
|
|
||||||
|
yyjson_mut_val *partyMembersArr = yyjson_mut_arr(doc);
|
||||||
|
for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) {
|
||||||
|
battlefighter_t *member = &slot->partyMembers[i];
|
||||||
|
writeArrayObject(partyMembersArr);
|
||||||
|
writeUInt32("id", member->id);
|
||||||
|
writeUInt32("status", member->status);
|
||||||
|
writeUInt32("team", member->team);
|
||||||
|
writeUInt32("controller", member->controller);
|
||||||
|
writeUInt32("health", member->health);
|
||||||
|
writeUInt32("healthMax", member->healthMax);
|
||||||
|
writeUInt32("mp", member->mp);
|
||||||
|
writeUInt32("mpMax", member->mpMax);
|
||||||
|
writeUInt32("statAttack", member->stats.attack);
|
||||||
|
writeUInt32("statDefense", member->stats.defense);
|
||||||
|
writeUInt32("statMagic", member->stats.magic);
|
||||||
|
writeUInt32("statSpeed", member->stats.speed);
|
||||||
|
writeUInt32("statLuck", member->stats.luck);
|
||||||
|
}
|
||||||
|
writeValue("partyMembers", partyMembersArr);
|
||||||
|
|
||||||
|
writeUInt8Array("partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,5 +64,40 @@ errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object) {
|
|||||||
"mapName", slot->mapName, "", SAVE_SLOT_VER1_MAP_NAME_MAX - 1
|
"mapName", slot->mapName, "", SAVE_SLOT_VER1_MAP_NAME_MAX - 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
requireArrayOfSize(
|
||||||
|
"partyMembers", partyMembersArr, PARTY_MEMBER_COUNT_MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
size_t memberIdx, memberLen;
|
||||||
|
yyjson_val *memberObj;
|
||||||
|
yyjson_arr_foreach(
|
||||||
|
partyMembersArr, memberIdx, memberLen, memberObj
|
||||||
|
) {
|
||||||
|
battlefighter_t *member = &slot->partyMembers[memberIdx];
|
||||||
|
// Shadows the outer `object` so readUInt32() below (which hardcodes
|
||||||
|
// that name) reads from this member's object instead.
|
||||||
|
yyjson_val *object = memberObj;
|
||||||
|
readUInt32("id", member->id, 0);
|
||||||
|
readUInt32("status", member->status, BATTLE_FIGHTER_STATUS_NULL);
|
||||||
|
readUInt32("team", member->team, BATTLE_FIGHTER_TEAM_ALLY);
|
||||||
|
readUInt32(
|
||||||
|
"controller", member->controller, BATTLE_FIGHTER_CONTROLLER_PLAYER
|
||||||
|
);
|
||||||
|
readUInt32("health", member->health, 0);
|
||||||
|
readUInt32("healthMax", member->healthMax, 0);
|
||||||
|
readUInt32("mp", member->mp, 0);
|
||||||
|
readUInt32("mpMax", member->mpMax, 0);
|
||||||
|
readUInt32("statAttack", member->stats.attack, 0);
|
||||||
|
readUInt32("statDefense", member->stats.defense, 0);
|
||||||
|
readUInt32("statMagic", member->stats.magic, 0);
|
||||||
|
readUInt32("statSpeed", member->stats.speed, 0);
|
||||||
|
readUInt32("statLuck", member->stats.luck, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
readUInt8Array(
|
||||||
|
"partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX,
|
||||||
|
PARTY_ORDER_EMPTY
|
||||||
|
);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,16 @@
|
|||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "time/timeepoch.h"
|
#include "time/timeepoch.h"
|
||||||
#include "save/slot/saveslotver.h"
|
#include "save/slot/saveslotver.h"
|
||||||
|
#include "rpg/battle/party.h"
|
||||||
#include "yyjson.h"
|
#include "yyjson.h"
|
||||||
|
|
||||||
// This schema's own frozen copies of save/slot/saveslot.h's field sizes -
|
// This schema's own frozen copies of save/slot/saveslot.h's field sizes -
|
||||||
// never reference SAVE_SLOT_NAME_LENGTH/MAP_NAME_MAX (or any other live
|
// never reference SAVE_SLOT_NAME_LENGTH/MAP_NAME_MAX (or any other live
|
||||||
// constant) here. Once shipped, this file's on-disk shape must never change
|
// constant) here. Once shipped, this file's on-disk shape must never change
|
||||||
// underneath old save files, even if the live engine constants do.
|
// underneath old save files, even if the live engine constants do. The
|
||||||
|
// party arrays below are the exception - saveSlotVer1ReadJSON() checks their
|
||||||
|
// JSON array lengths explicitly, so PARTY_MEMBER_COUNT_MAX/
|
||||||
|
// PARTY_ACTIVE_SIZE_MAX (rpg/battle/party.h) can be referenced directly.
|
||||||
#define SAVE_SLOT_VER1_NAME_LENGTH 8
|
#define SAVE_SLOT_VER1_NAME_LENGTH 8
|
||||||
#define SAVE_SLOT_VER1_MAP_NAME_MAX 10
|
#define SAVE_SLOT_VER1_MAP_NAME_MAX 10
|
||||||
|
|
||||||
@@ -28,6 +32,9 @@ typedef struct saveslotver1_s {
|
|||||||
dusktimeepoch_t time;
|
dusktimeepoch_t time;
|
||||||
int32_t playerLevel;
|
int32_t playerLevel;
|
||||||
char_t mapName[SAVE_SLOT_VER1_MAP_NAME_MAX];
|
char_t mapName[SAVE_SLOT_VER1_MAP_NAME_MAX];
|
||||||
|
|
||||||
|
battlefighter_t partyMembers[PARTY_MEMBER_COUNT_MAX];
|
||||||
|
uint8_t partyOrder[PARTY_ACTIVE_SIZE_MAX];
|
||||||
} saveslotver1_t;
|
} saveslotver1_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ void uiSaveSlotSetHighlighted(
|
|||||||
saveSlot->highlighted = highlighted;
|
saveSlot->highlighted = highlighted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool_t uiSaveSlotIsCorrupt(const uisaveslot_t *saveSlot) {
|
||||||
|
assertNotNull(saveSlot, "Save slot cannot be NULL");
|
||||||
|
return saveSlot->corrupt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiSaveSlotSetCorrupt(uisaveslot_t *saveSlot, const bool_t corrupt) {
|
||||||
|
assertNotNull(saveSlot, "Save slot cannot be NULL");
|
||||||
|
saveSlot->corrupt = corrupt;
|
||||||
|
}
|
||||||
|
|
||||||
uisaveslotdisplaytype_t uiSaveSlotGetDisplayType(const uisaveslot_t *saveSlot) {
|
uisaveslotdisplaytype_t uiSaveSlotGetDisplayType(const uisaveslot_t *saveSlot) {
|
||||||
assertNotNull(saveSlot, "Save slot cannot be NULL");
|
assertNotNull(saveSlot, "Save slot cannot be NULL");
|
||||||
return saveSlot->displayType;
|
return saveSlot->displayType;
|
||||||
@@ -85,7 +95,9 @@ errorret_t uiSaveSlotDraw(
|
|||||||
const float_t contentY = y + UI_FRAME_START_Y;
|
const float_t contentY = y + UI_FRAME_START_Y;
|
||||||
|
|
||||||
color_t color;
|
color_t color;
|
||||||
if(saveSlot->highlighted) {
|
if(saveSlot->corrupt) {
|
||||||
|
color = COLOR_RED;
|
||||||
|
} else if(saveSlot->highlighted) {
|
||||||
color = COLOR_RED;
|
color = COLOR_RED;
|
||||||
} else if(saveSlot->displayType == UI_SAVE_SLOT_DISPLAY_TYPE_DELETE) {
|
} else if(saveSlot->displayType == UI_SAVE_SLOT_DISPLAY_TYPE_DELETE) {
|
||||||
color = COLOR_ORANGE;
|
color = COLOR_ORANGE;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ typedef struct {
|
|||||||
spritebatchsprite_t nameSprites[UI_SAVE_SLOT_NAME_SPRITES_MAX];
|
spritebatchsprite_t nameSprites[UI_SAVE_SLOT_NAME_SPRITES_MAX];
|
||||||
|
|
||||||
bool_t highlighted;
|
bool_t highlighted;
|
||||||
|
bool_t corrupt;
|
||||||
} uisaveslot_t;
|
} uisaveslot_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,6 +76,23 @@ bool_t uiSaveSlotIsHighlighted(const uisaveslot_t *saveSlot);
|
|||||||
*/
|
*/
|
||||||
void uiSaveSlotSetHighlighted(uisaveslot_t *saveSlot, const bool_t highlighted);
|
void uiSaveSlotSetHighlighted(uisaveslot_t *saveSlot, const bool_t highlighted);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the save slot widget is flagged as corrupt.
|
||||||
|
*
|
||||||
|
* @param saveSlot The save slot widget to query.
|
||||||
|
* @returns True if flagged as corrupt.
|
||||||
|
*/
|
||||||
|
bool_t uiSaveSlotIsCorrupt(const uisaveslot_t *saveSlot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the save slot widget is flagged as corrupt - drawn in red
|
||||||
|
* regardless of highlighted/display-type state, taking priority over both.
|
||||||
|
*
|
||||||
|
* @param saveSlot The save slot widget to update.
|
||||||
|
* @param corrupt The new corrupt state.
|
||||||
|
*/
|
||||||
|
void uiSaveSlotSetCorrupt(uisaveslot_t *saveSlot, const bool_t corrupt);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the save slot widget's display type.
|
* Returns the save slot widget's display type.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "uiselectsave.h"
|
#include "uiselectsave.h"
|
||||||
#include "ui/widget/uiframe.h"
|
#include "ui/widget/uiframe.h"
|
||||||
|
#include "ui/widget/uimodal.h"
|
||||||
#include "ui/dialog/uiconfirm.h"
|
#include "ui/dialog/uiconfirm.h"
|
||||||
#include "ui/dialog/keyboard/uikeyboard.h"
|
#include "ui/dialog/keyboard/uikeyboard.h"
|
||||||
#include "ui/overlay/uifatalerror.h"
|
#include "ui/overlay/uifatalerror.h"
|
||||||
@@ -31,6 +32,14 @@ void uiSelectSaveFormatSlot(
|
|||||||
) {
|
) {
|
||||||
saveslotcache_t *cache = &SAVE.caches[index];
|
saveslotcache_t *cache = &SAVE.caches[index];
|
||||||
|
|
||||||
|
if(cache->corrupt) {
|
||||||
|
errorret_t result = localeManagerGetString(
|
||||||
|
"ui.select_save.corrupt", buffer, bufferSize, 0
|
||||||
|
);
|
||||||
|
if(errorIsNotOk(result)) errorCatch(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!saveSlotInUse(cache)) {
|
if(!saveSlotInUse(cache)) {
|
||||||
errorret_t result = localeManagerGetString(
|
errorret_t result = localeManagerGetString(
|
||||||
"ui.select_save.empty", buffer, bufferSize, 0
|
"ui.select_save.empty", buffer, bufferSize, 0
|
||||||
@@ -58,6 +67,9 @@ void uiSelectSaveRefreshSlot(const uint8_t index) {
|
|||||||
UI_SELECT_SAVE.type == UI_SELECT_SAVE_TYPE_DELETE ?
|
UI_SELECT_SAVE.type == UI_SELECT_SAVE_TYPE_DELETE ?
|
||||||
UI_SAVE_SLOT_DISPLAY_TYPE_DELETE : UI_SAVE_SLOT_DISPLAY_TYPE_NORMAL;
|
UI_SAVE_SLOT_DISPLAY_TYPE_DELETE : UI_SAVE_SLOT_DISPLAY_TYPE_NORMAL;
|
||||||
uiSaveSlotSetDisplayType(&UI_SELECT_SAVE.slots[index], displayType);
|
uiSaveSlotSetDisplayType(&UI_SELECT_SAVE.slots[index], displayType);
|
||||||
|
uiSaveSlotSetCorrupt(
|
||||||
|
&UI_SELECT_SAVE.slots[index], SAVE.caches[index].corrupt
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiSelectSaveSetActionLabel(const uiselectsavetype_t type) {
|
void uiSelectSaveSetActionLabel(const uiselectsavetype_t type) {
|
||||||
@@ -170,6 +182,18 @@ void uiSelectSaveNameEmptySlot(const uint8_t index) {
|
|||||||
uiKeyboardOpen(&open);
|
uiKeyboardOpen(&open);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void uiSelectSaveWarnCorrupt(void) {
|
||||||
|
const char_t *options[] = { "ui.confirm.confirm" };
|
||||||
|
// Acknowledging just closes the warning - the player stays on the
|
||||||
|
// select-save list rather than being pushed into naming/overwriting
|
||||||
|
// the slot.
|
||||||
|
uiModalOpen(
|
||||||
|
"ui.select_save.corrupt", "ui.select_save.corrupt_warning",
|
||||||
|
options, 1,
|
||||||
|
NULL, NULL, NULL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool_t uiSelectSaveMenuCancelled(const uimenu_t *menu) {
|
bool_t uiSelectSaveMenuCancelled(const uimenu_t *menu) {
|
||||||
if(UI_SELECT_SAVE.type != UI_SELECT_SAVE_TYPE_DELETE) return false;
|
if(UI_SELECT_SAVE.type != UI_SELECT_SAVE_TYPE_DELETE) return false;
|
||||||
|
|
||||||
@@ -195,6 +219,11 @@ void uiSelectSaveSlotSelected(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(SAVE.caches[index].corrupt) {
|
||||||
|
uiSelectSaveWarnCorrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!saveSlotInUse(&SAVE.caches[index])) {
|
if(!saveSlotInUse(&SAVE.caches[index])) {
|
||||||
uiSelectSaveNameEmptySlot(index);
|
uiSelectSaveNameEmptySlot(index);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user