Add campaign battle rewards
This commit is contained in:
@@ -378,6 +378,16 @@ export type BattleSortieDefinition = {
|
||||
note: string;
|
||||
};
|
||||
|
||||
export type BattleCampaignRewardDefinition = {
|
||||
supplies?: string[];
|
||||
equipment?: string[];
|
||||
reputation?: string[];
|
||||
recruits?: string[];
|
||||
unlockBattleId?: BattleScenarioId;
|
||||
unlockLabel?: string;
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type BattleScenarioDefinition = {
|
||||
id: BattleScenarioId;
|
||||
title: string;
|
||||
@@ -395,6 +405,7 @@ export type BattleScenarioDefinition = {
|
||||
objectives: BattleObjectiveDefinition[];
|
||||
defeatConditions: BattleDefeatConditionDefinition[];
|
||||
itemRewards: string[];
|
||||
campaignReward?: BattleCampaignRewardDefinition;
|
||||
victoryPages: StoryPage[];
|
||||
nextCampScene: string;
|
||||
};
|
||||
@@ -448,6 +459,15 @@ export const firstBattleScenario: BattleScenarioDefinition = {
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 1', '탁주 1', '의용군 명성 +1'],
|
||||
campaignReward: {
|
||||
supplies: ['콩 1', '탁주 1'],
|
||||
equipment: ['연습검 1'],
|
||||
reputation: ['의용군 명성 +1'],
|
||||
recruits: ['guan-yu', 'zhang-fei'],
|
||||
unlockBattleId: 'second-battle-yellow-turban-pursuit',
|
||||
unlockLabel: '황건 잔당 추격 개방',
|
||||
note: '도원 형제의 의용군이 정식으로 출전 가능한 부대로 정리됩니다.'
|
||||
},
|
||||
victoryPages: firstBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
@@ -521,6 +541,14 @@ export const secondBattleScenario: BattleScenarioDefinition = {
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 2', '상처약 1', '탁주 1', '의용군 명성 +2'],
|
||||
campaignReward: {
|
||||
supplies: ['콩 2', '상처약 1', '탁주 1'],
|
||||
equipment: ['황건 부적 1'],
|
||||
reputation: ['의용군 명성 +2'],
|
||||
unlockBattleId: 'third-battle-guangzong-road',
|
||||
unlockLabel: '광종 구원로 개방',
|
||||
note: '북쪽 마을 보급과 잔당 추격 기록이 다음 전투 준비에 반영됩니다.'
|
||||
},
|
||||
victoryPages: secondBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
@@ -594,6 +622,14 @@ export const thirdBattleScenario: BattleScenarioDefinition = {
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 2', '상처약 1', '의용군 명성 +3'],
|
||||
campaignReward: {
|
||||
supplies: ['콩 2', '상처약 1'],
|
||||
equipment: ['단궁 1'],
|
||||
reputation: ['의용군 명성 +3'],
|
||||
unlockBattleId: 'fourth-battle-guangzong-camp',
|
||||
unlockLabel: '광종 본영전 개방',
|
||||
note: '광종 구원로 확보로 본영 진입 정보가 열립니다.'
|
||||
},
|
||||
victoryPages: thirdBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
@@ -667,6 +703,14 @@ export const fourthBattleScenario: BattleScenarioDefinition = {
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 2', '상처약 1', '탁주 1', '황건 토벌 공적 +1'],
|
||||
campaignReward: {
|
||||
supplies: ['콩 2', '상처약 1', '탁주 1'],
|
||||
equipment: ['황건도 1'],
|
||||
reputation: ['황건 토벌 공적 +1'],
|
||||
unlockBattleId: 'fifth-battle-sishui-vanguard',
|
||||
unlockLabel: '사수관 전초전 개방',
|
||||
note: '황건 본영 토벌 공적이 반동탁 연합 합류 명분으로 이어집니다.'
|
||||
},
|
||||
victoryPages: fourthBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
@@ -740,6 +784,14 @@ export const fifthBattleScenario: BattleScenarioDefinition = {
|
||||
],
|
||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||
itemRewards: ['콩 2', '상처약 1', '탁주 1', '반동탁 공적 +1'],
|
||||
campaignReward: {
|
||||
supplies: ['콩 2', '상처약 1', '탁주 1'],
|
||||
equipment: ['찰갑 1'],
|
||||
reputation: ['반동탁 공적 +1'],
|
||||
unlockBattleId: 'sixth-battle-jieqiao-relief',
|
||||
unlockLabel: '계교 원군로 개방',
|
||||
note: '사수관 전초 진지의 전과가 공손찬 진영의 신뢰로 연결됩니다.'
|
||||
},
|
||||
victoryPages: fifthBattleVictoryPages,
|
||||
nextCampScene: 'CampScene'
|
||||
};
|
||||
|
||||
@@ -2,7 +2,13 @@ import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { battleMapAssets } from '../data/battleMapAssets';
|
||||
import { type BattleBond, type UnitData, type UnitStats } from '../data/scenario';
|
||||
import { defaultBattleScenario, getBattleScenario, type BattleObjectiveDefinition, type BattleScenarioDefinition } from '../data/battles';
|
||||
import {
|
||||
defaultBattleScenario,
|
||||
getBattleScenario,
|
||||
type BattleCampaignRewardDefinition,
|
||||
type BattleObjectiveDefinition,
|
||||
type BattleScenarioDefinition
|
||||
} from '../data/battles';
|
||||
import { getTerrainRule, getUnitClass, type TerrainType, type UnitClassKey } from '../data/battleRules';
|
||||
import {
|
||||
ensureUnitAnimations,
|
||||
@@ -41,6 +47,7 @@ import {
|
||||
markCampaignStep,
|
||||
saveCampaignState,
|
||||
setFirstBattleReport,
|
||||
type CampaignRewardSnapshot,
|
||||
type CampaignState,
|
||||
type CampaignStep
|
||||
} from '../state/campaignState';
|
||||
@@ -3179,6 +3186,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.drawDebugSpritePreview();
|
||||
this.updateTurnText();
|
||||
this.time.delayedCall(180, () => this.showOpeningBattleEvent());
|
||||
this.scheduleDebugForcedBattleOutcome();
|
||||
this.renderRosterPanel('ally', '행동할 장수를 선택하세요.');
|
||||
}
|
||||
|
||||
@@ -7573,6 +7581,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
const totalEnemies = battleUnits.filter((unit) => unit.faction === 'enemy').length;
|
||||
const aliveAllies = allies.filter((unit) => unit.hp > 0).length;
|
||||
const objectives = this.resultObjectives(outcome);
|
||||
const rewardSnapshot = this.resultCampaignRewards(outcome);
|
||||
|
||||
const shade = this.trackResultObject(this.add.rectangle(0, 0, this.scale.width, this.scale.height, 0x020406, 0.68));
|
||||
shade.setOrigin(0);
|
||||
@@ -7595,10 +7604,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
title.setOrigin(0.5, 0);
|
||||
title.setDepth(depth + 2);
|
||||
|
||||
const subtitleText =
|
||||
outcome === 'victory'
|
||||
? '탁현 의용군은 첫 싸움에서 황건적을 몰아내고 마을을 지켜냈습니다.'
|
||||
: '유비가 전장을 이탈했습니다. 진형을 가다듬고 다시 도전해야 합니다.';
|
||||
const subtitleText = this.resultSubtitle(outcome, rewardSnapshot);
|
||||
const subtitle = this.trackResultObject(this.add.text(left + panelWidth / 2, top + 72, subtitleText, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '17px',
|
||||
@@ -7617,7 +7623,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.renderResultMetric('전투 보상', outcome === 'victory' ? `군자금 ${this.resultGold(outcome)}` : '없음', left + 44 + (metricWidth + metricGap) * 3, metricTop, metricWidth, depth + 2);
|
||||
|
||||
this.renderResultObjectivePanel(objectives, left + 44, top + 178, 456, depth + 2);
|
||||
this.renderResultRewardPanel(outcome, objectives, left + 526, top + 178, 470, depth + 2);
|
||||
this.renderResultRewardPanel(outcome, objectives, rewardSnapshot, left + 526, top + 178, 470, depth + 2);
|
||||
this.renderResultBondStrip(left + 44, top + 318, panelWidth - 88, depth + 2);
|
||||
|
||||
const unitTitle = this.trackResultObject(this.add.text(left + 44, top + 354, '장수 성장', {
|
||||
@@ -7764,6 +7770,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
private publishFirstBattleReport(outcome: BattleOutcome) {
|
||||
const objectives = this.resultObjectives(outcome);
|
||||
const mvp = this.resultMvp();
|
||||
const rewardSnapshot = this.resultCampaignRewards(outcome);
|
||||
const defeatedEnemies = battleUnits.filter((unit) => unit.faction === 'enemy' && unit.hp <= 0).length;
|
||||
const totalEnemies = battleUnits.filter((unit) => unit.faction === 'enemy').length;
|
||||
|
||||
@@ -7789,13 +7796,85 @@ export class BattleScene extends Phaser.Scene {
|
||||
defeats: mvp.stats.defeats
|
||||
}
|
||||
: undefined,
|
||||
itemRewards: outcome === 'victory' ? [...battleScenario.itemRewards] : [],
|
||||
itemRewards: this.resultItemRewards(outcome),
|
||||
campaignRewards: rewardSnapshot,
|
||||
completedCampDialogues: [],
|
||||
completedCampVisits: [],
|
||||
createdAt: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
private resultSubtitle(outcome: BattleOutcome, rewards: CampaignRewardSnapshot) {
|
||||
if (outcome !== 'victory') {
|
||||
return `${battleScenario.title}에서 패배했습니다. 진형과 보급을 정비한 뒤 다시 도전해야 합니다.`;
|
||||
}
|
||||
|
||||
const unlock = rewards.unlocks[0]?.title;
|
||||
return unlock
|
||||
? `${battleScenario.title} 승리. 보상 정산 후 ${unlock}로 이어집니다.`
|
||||
: `${battleScenario.title} 승리. 획득 보상과 장수 성장을 정산합니다.`;
|
||||
}
|
||||
|
||||
private resultItemRewards(outcome: BattleOutcome) {
|
||||
if (outcome !== 'victory') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const reward = battleScenario.campaignReward;
|
||||
return this.uniqueRewardLabels([
|
||||
...battleScenario.itemRewards,
|
||||
...(reward?.supplies ?? []),
|
||||
...(reward?.equipment ?? []),
|
||||
...(reward?.reputation ?? [])
|
||||
]);
|
||||
}
|
||||
|
||||
private resultCampaignRewards(outcome: BattleOutcome): CampaignRewardSnapshot {
|
||||
if (outcome !== 'victory') {
|
||||
return {
|
||||
supplies: [],
|
||||
equipment: [],
|
||||
reputation: [],
|
||||
recruits: [],
|
||||
unlocks: []
|
||||
};
|
||||
}
|
||||
|
||||
const reward: BattleCampaignRewardDefinition | undefined = battleScenario.campaignReward;
|
||||
const unlocks = reward?.unlockBattleId
|
||||
? [
|
||||
{
|
||||
battleId: reward.unlockBattleId,
|
||||
title: reward.unlockLabel ?? getBattleScenario(reward.unlockBattleId).title
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
return {
|
||||
supplies: this.uniqueRewardLabels(reward?.supplies ?? this.supplyRewardLabels(battleScenario.itemRewards)),
|
||||
equipment: this.uniqueRewardLabels(reward?.equipment ?? []),
|
||||
reputation: this.uniqueRewardLabels(reward?.reputation ?? this.reputationRewardLabels(battleScenario.itemRewards)),
|
||||
recruits: (reward?.recruits ?? []).map((unitId) => ({
|
||||
unitId,
|
||||
name: this.unitName(unitId)
|
||||
})),
|
||||
unlocks,
|
||||
note: reward?.note
|
||||
};
|
||||
}
|
||||
|
||||
private uniqueRewardLabels(labels: string[]) {
|
||||
return [...new Set(labels.map((label) => label.trim()).filter(Boolean))];
|
||||
}
|
||||
|
||||
private supplyRewardLabels(labels: string[]) {
|
||||
return labels.filter((label) => /콩|상처약|탁주/.test(label));
|
||||
}
|
||||
|
||||
private reputationRewardLabels(labels: string[]) {
|
||||
return labels.filter((label) => !/콩|상처약|탁주/.test(label));
|
||||
}
|
||||
|
||||
private renderResultObjectivePanel(objectives: BattleObjectiveResult[], x: number, y: number, width: number, depth: number) {
|
||||
const height = 132;
|
||||
const bg = this.trackResultObject(this.add.rectangle(x, y, width, height, 0x101820, 0.9));
|
||||
@@ -7834,7 +7913,15 @@ export class BattleScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private renderResultRewardPanel(outcome: BattleOutcome, objectives: BattleObjectiveResult[], x: number, y: number, width: number, depth: number) {
|
||||
private renderResultRewardPanel(
|
||||
outcome: BattleOutcome,
|
||||
objectives: BattleObjectiveResult[],
|
||||
rewards: CampaignRewardSnapshot,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
depth: number
|
||||
) {
|
||||
const height = 132;
|
||||
const bg = this.trackResultObject(this.add.rectangle(x, y, width, height, 0x101820, 0.9));
|
||||
bg.setOrigin(0);
|
||||
@@ -7855,11 +7942,21 @@ export class BattleScene extends Phaser.Scene {
|
||||
.map((bond) => `${this.unitName(bond.unitIds[0])}·${this.unitName(bond.unitIds[1])} +${bond.battleExp}`)
|
||||
.join(' ');
|
||||
const achievedCount = objectives.filter((objective) => objective.achieved).length;
|
||||
const itemRewards = this.resultItemRewards(outcome);
|
||||
const itemLine = [
|
||||
rewards.supplies.length > 0 ? `보급 ${rewards.supplies.join(', ')}` : '',
|
||||
rewards.equipment.length > 0 ? `장비 ${rewards.equipment.join(', ')}` : '',
|
||||
rewards.reputation.length > 0 ? `명성 ${rewards.reputation.join(', ')}` : ''
|
||||
].filter(Boolean).join(' · ');
|
||||
const progressLine = [
|
||||
rewards.recruits.length > 0 ? `합류 ${rewards.recruits.map((recruit) => recruit.name).join(', ')}` : '',
|
||||
rewards.unlocks.length > 0 ? `해금 ${rewards.unlocks.map((unlock) => unlock.title).join(', ')}` : ''
|
||||
].filter(Boolean).join(' · ');
|
||||
const lines = [
|
||||
outcome === 'victory' ? `군자금 ${this.resultGold(outcome)} 목표 보상 ${achievedCount}/${objectives.length}` : '패배: 보상 없음',
|
||||
mvp ? `MVP ${mvp.unit.name}: 피해 ${mvp.stats.damageDealt}, 격파 ${mvp.stats.defeats}` : 'MVP 없음',
|
||||
bondSummary ? `공명 성장 ${bondSummary}` : '공명 성장 없음',
|
||||
outcome === 'victory' ? `전리품: ${battleScenario.itemRewards.join(', ')}` : '재도전하면 목표 보상을 다시 노릴 수 있습니다.'
|
||||
outcome === 'victory' ? (itemLine || `전리품 ${itemRewards.join(', ') || '없음'}`) : '재도전하면 목표 보상을 다시 노릴 수 있습니다.',
|
||||
outcome === 'victory' ? (progressLine || rewards.note || '새 해금 없음') : (bondSummary ? `공명 성장 ${bondSummary}` : '공명 성장 없음')
|
||||
];
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
@@ -15427,6 +15524,24 @@ export class BattleScene extends Phaser.Scene {
|
||||
.filter((preview): preview is NonNullable<typeof preview> => Boolean(preview));
|
||||
}
|
||||
|
||||
private scheduleDebugForcedBattleOutcome() {
|
||||
const outcome = this.debugForcedBattleOutcomeParam();
|
||||
if (!outcome) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.time.delayedCall(900, () => this.debugForceBattleOutcome(outcome));
|
||||
}
|
||||
|
||||
private debugForcedBattleOutcomeParam(): BattleOutcome | undefined {
|
||||
if (!this.debugToolsEnabled() || typeof window === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const value = new URLSearchParams(window.location.search).get('debugForceBattleOutcome')?.trim();
|
||||
return value === 'victory' || value === 'defeat' ? value : undefined;
|
||||
}
|
||||
|
||||
debugForceBattleOutcome(outcome: BattleOutcome = 'victory') {
|
||||
if (!this.debugToolsEnabled() || this.battleOutcome) {
|
||||
return;
|
||||
|
||||
@@ -12774,11 +12774,38 @@ export class CampScene extends Phaser.Scene {
|
||||
this.track(
|
||||
this.add.text(
|
||||
x + 18,
|
||||
y + 43,
|
||||
y + 39,
|
||||
`MVP ${report.mvp?.name ?? '-'} · 전투 보상 ${report.rewardGold} · 전리품 ${report.itemRewards.join(', ') || '없음'}`,
|
||||
this.textStyle(15, '#d4dce6')
|
||||
this.textStyle(14, '#d4dce6')
|
||||
)
|
||||
);
|
||||
this.track(
|
||||
this.add.text(
|
||||
x + 18,
|
||||
y + 62,
|
||||
this.reportCampaignRewardLine(report),
|
||||
this.textStyle(12, '#d8b15f', true)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private reportCampaignRewardLine(report: FirstBattleReport) {
|
||||
const rewards = report.campaignRewards;
|
||||
if (!rewards) {
|
||||
return '캠페인 보상: 기존 전리품이 군영 장부에 반영되었습니다.';
|
||||
}
|
||||
|
||||
const recruits = rewards.recruits ?? [];
|
||||
const equipment = rewards.equipment ?? [];
|
||||
const reputation = rewards.reputation ?? [];
|
||||
const unlocks = rewards.unlocks ?? [];
|
||||
const parts = [
|
||||
recruits.length > 0 ? `합류 ${recruits.map((recruit) => recruit.name).join(', ')}` : '',
|
||||
equipment.length > 0 ? `장비 ${equipment.join(', ')}` : '',
|
||||
reputation.length > 0 ? `명성 ${reputation.join(', ')}` : '',
|
||||
unlocks.length > 0 ? `다음 ${unlocks.map((unlock) => unlock.title).join(', ')}` : ''
|
||||
].filter(Boolean);
|
||||
return parts.length > 0 ? `캠페인 반영: ${parts.join(' · ')}` : '캠페인 보상: 추가 해금 없음';
|
||||
}
|
||||
|
||||
private renderProgressPanel() {
|
||||
|
||||
@@ -19,6 +19,14 @@ export class TitleScene extends Phaser.Scene {
|
||||
this.settingsPanel = undefined;
|
||||
this.navigating = false;
|
||||
|
||||
const debugBattleId = this.debugBattleId();
|
||||
if (debugBattleId) {
|
||||
this.time.delayedCall(0, () => {
|
||||
void this.navigateTo('BattleScene', { battleId: debugBattleId });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shouldOpenDebugSortiePrep()) {
|
||||
this.time.delayedCall(0, () => {
|
||||
void this.navigateTo('CampScene', {
|
||||
@@ -55,6 +63,17 @@ export class TitleScene extends Phaser.Scene {
|
||||
return params.has('debugSortiePrep') && (params.has('debug') || import.meta.env.DEV);
|
||||
}
|
||||
|
||||
private debugBattleId() {
|
||||
if (typeof window === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (!params.has('debugBattle') || (!params.has('debug') && !import.meta.env.DEV)) {
|
||||
return undefined;
|
||||
}
|
||||
return params.get('debugBattle')?.trim() || undefined;
|
||||
}
|
||||
|
||||
private drawBackground(width: number, height: number) {
|
||||
const background = this.add.image(width / 2, height / 2, 'title-taoyuan');
|
||||
const texture = this.textures.get('title-taoyuan').getSourceImage();
|
||||
|
||||
@@ -21,6 +21,21 @@ export type CampMvpSnapshot = {
|
||||
defeats: number;
|
||||
};
|
||||
|
||||
export type CampaignRewardSnapshot = {
|
||||
supplies: string[];
|
||||
equipment: string[];
|
||||
reputation: string[];
|
||||
recruits: {
|
||||
unitId: string;
|
||||
name: string;
|
||||
}[];
|
||||
unlocks: {
|
||||
battleId: string;
|
||||
title: string;
|
||||
}[];
|
||||
note?: string;
|
||||
};
|
||||
|
||||
export type FirstBattleReport = {
|
||||
battleId: string;
|
||||
battleTitle: string;
|
||||
@@ -34,6 +49,7 @@ export type FirstBattleReport = {
|
||||
bonds: CampBondSnapshot[];
|
||||
mvp?: CampMvpSnapshot;
|
||||
itemRewards: string[];
|
||||
campaignRewards?: CampaignRewardSnapshot;
|
||||
completedCampDialogues: string[];
|
||||
completedCampVisits: string[];
|
||||
createdAt: string;
|
||||
@@ -263,6 +279,7 @@ export type CampaignBattleSettlement = {
|
||||
outcome: FirstBattleReport['outcome'];
|
||||
rewardGold: number;
|
||||
itemRewards: string[];
|
||||
campaignRewards?: CampaignRewardSnapshot;
|
||||
objectives: BattleObjectiveSnapshot[];
|
||||
units: CampaignUnitProgressSnapshot[];
|
||||
bonds: CampaignBondProgressSnapshot[];
|
||||
@@ -494,7 +511,10 @@ export function setFirstBattleReport(report: FirstBattleReport) {
|
||||
|
||||
state.step = victoryStep;
|
||||
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 alliedReportUnits = reportClone.units.filter((unit) => unit.faction === 'ally');
|
||||
const recruitUnitIds = new Set(reportClone.campaignRewards?.recruits.map((recruit) => recruit.unitId) ?? []);
|
||||
const recruitedUnits = reportClone.units.filter((unit) => unit.faction === 'ally' && recruitUnitIds.has(unit.id));
|
||||
state.roster = mergeRosterProgress(state.roster, [...alliedReportUnits, ...recruitedUnits]);
|
||||
state.bonds = reportClone.bonds.map(cloneBondSnapshot);
|
||||
const reserveTraining = previousSettlement || reportClone.outcome !== 'victory'
|
||||
? previousSettlement?.reserveTraining ?? []
|
||||
@@ -753,6 +773,7 @@ function createBattleSettlement(report: FirstBattleReport, reserveTraining: Camp
|
||||
outcome: report.outcome,
|
||||
rewardGold: report.rewardGold,
|
||||
itemRewards: [...report.itemRewards],
|
||||
campaignRewards: cloneCampaignRewardSnapshot(report.campaignRewards),
|
||||
objectives: report.objectives.map((objective) => ({ ...objective })),
|
||||
units: report.units
|
||||
.filter((unit) => unit.faction === 'ally')
|
||||
@@ -887,9 +908,27 @@ function cloneCampaignState(state: CampaignState): CampaignState {
|
||||
function cloneReport(report: FirstBattleReport): FirstBattleReport {
|
||||
const cloned = JSON.parse(JSON.stringify(report)) as FirstBattleReport;
|
||||
cloned.completedCampVisits = [...new Set(cloned.completedCampVisits ?? [])];
|
||||
if (cloned.campaignRewards) {
|
||||
cloned.campaignRewards = cloneCampaignRewardSnapshot(cloned.campaignRewards);
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
function cloneCampaignRewardSnapshot(rewards?: CampaignRewardSnapshot): CampaignRewardSnapshot | undefined {
|
||||
if (!rewards) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
supplies: [...(rewards.supplies ?? [])],
|
||||
equipment: [...(rewards.equipment ?? [])],
|
||||
reputation: [...(rewards.reputation ?? [])],
|
||||
recruits: (rewards.recruits ?? []).map((recruit) => ({ ...recruit })),
|
||||
unlocks: (rewards.unlocks ?? []).map((unlock) => ({ ...unlock })),
|
||||
note: rewards.note
|
||||
};
|
||||
}
|
||||
|
||||
function cloneUnit(unit: UnitData): UnitData {
|
||||
return JSON.parse(JSON.stringify(unit)) as UnitData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user