Add officer roster reserve growth

This commit is contained in:
2026-06-23 05:59:14 +09:00
parent 44009b3542
commit 8f083a6ca4
4 changed files with 195 additions and 19 deletions

View File

@@ -1,3 +1,4 @@
import { equipmentExpToNext, equipmentSlots } from '../data/battleItems';
import type { BattleBond, UnitData } from '../data/scenario';
export type BattleObjectiveSnapshot = {
@@ -96,6 +97,15 @@ export type CampaignBondProgressSnapshot = {
battleExp: number;
};
export type CampaignReserveTrainingSnapshot = {
unitId: string;
name: string;
expGained: number;
equipmentExpGained: number;
level: number;
exp: number;
};
export type CampaignBattleSettlement = {
battleId: string;
battleTitle: string;
@@ -105,6 +115,7 @@ export type CampaignBattleSettlement = {
objectives: BattleObjectiveSnapshot[];
units: CampaignUnitProgressSnapshot[];
bonds: CampaignBondProgressSnapshot[];
reserveTraining?: CampaignReserveTrainingSnapshot[];
completedAt: string;
};
@@ -265,12 +276,15 @@ export function setFirstBattleReport(report: FirstBattleReport) {
state.step = reportClone.outcome === 'victory' ? victoryStep : retryStep;
state.gold = Math.max(0, state.gold - (previousSettlement?.rewardGold ?? 0) + reportClone.rewardGold);
state.roster = mergeRosterProgress(state.roster, reportClone.units.filter((unit) => unit.faction === 'ally'));
const reserveTraining = previousSettlement || reportClone.outcome !== 'victory'
? previousSettlement?.reserveTraining ?? []
: applyReserveTrainingProgress(state.roster, reportClone.units.filter((unit) => unit.faction === 'ally'));
state.bonds = reportClone.bonds.map(cloneBondSnapshot);
state.completedCampDialogues = completedCampDialogues;
state.completedCampVisits = completedCampVisits;
state.inventory = applyRewardDelta(state.inventory, previousSettlement?.itemRewards ?? [], -1);
state.inventory = applyRewardDelta(state.inventory, reportClone.itemRewards, 1);
state.battleHistory[battleId] = createBattleSettlement(reportClone);
state.battleHistory[battleId] = createBattleSettlement(reportClone, reserveTraining);
state.latestBattleId = battleId;
state.updatedAt = new Date().toISOString();
@@ -492,7 +506,7 @@ function advanceBond(bond: CampBondSnapshot, amount: number) {
}
}
function createBattleSettlement(report: FirstBattleReport): CampaignBattleSettlement {
function createBattleSettlement(report: FirstBattleReport, reserveTraining: CampaignReserveTrainingSnapshot[] = []): CampaignBattleSettlement {
return {
battleId: report.battleId,
battleTitle: report.battleTitle,
@@ -518,6 +532,7 @@ function createBattleSettlement(report: FirstBattleReport): CampaignBattleSettle
exp: bond.exp,
battleExp: bond.battleExp
})),
reserveTraining: reserveTraining.map((entry) => ({ ...entry })),
completedAt: report.createdAt
};
}
@@ -540,6 +555,50 @@ function mergeRosterProgress(currentRoster: UnitData[], deployedUnits: UnitData[
return Array.from(merged.values());
}
function applyReserveTrainingProgress(roster: UnitData[], deployedUnits: UnitData[]): CampaignReserveTrainingSnapshot[] {
const deployedIds = new Set(deployedUnits.map((unit) => unit.id));
return roster
.filter((unit) => unit.faction === 'ally' && !deployedIds.has(unit.id))
.map((unit) => {
const expGained = 6;
const equipmentExpGained = 2;
advanceUnitExp(unit, expGained);
advanceReserveEquipment(unit, equipmentExpGained);
return {
unitId: unit.id,
name: unit.name,
expGained,
equipmentExpGained,
level: unit.level,
exp: unit.exp
};
});
}
function advanceUnitExp(unit: UnitData, amount: number) {
let exp = unit.exp + amount;
while (unit.level < 99 && exp >= 100) {
exp -= 100;
unit.level += 1;
unit.maxHp += 2;
unit.hp = Math.min(unit.maxHp, unit.hp + 2);
unit.attack += 1;
}
unit.exp = unit.level >= 99 ? Math.min(exp, 100) : exp;
}
function advanceReserveEquipment(unit: UnitData, amount: number) {
equipmentSlots.forEach((slot) => {
const state = unit.equipment[slot];
let exp = state.exp + amount;
while (state.level < 9 && exp >= equipmentExpToNext(state.level)) {
exp -= equipmentExpToNext(state.level);
state.level += 1;
}
state.exp = state.level >= 9 ? Math.min(exp, equipmentExpToNext(state.level)) : exp;
});
}
function parseRewardLabel(reward: string) {
const normalized = reward.replace(/\s+/g, ' ').trim();
const match = normalized.match(/^(.*?)(?:\s*[+xX]?\s*(\d+))$/);