feat: make third-camp preparation choices explicit
This commit is contained in:
@@ -333,7 +333,7 @@ export function resolveThirdCampExplorationDefinition(
|
||||
subtitle:
|
||||
'노식군의 큰 포위망 안에서 의용군은 동쪽 삼림 선봉을 맡습니다. 필요한 준비만 확인하고 언제든 출진할 수 있습니다.',
|
||||
description:
|
||||
'작전판에서 적 선봉의 움직임을 읽고, 보급 수레에서 방호구를 손보거나, 먼저 돌아온 동료와 지난 전투를 되짚을 수 있습니다. 세 활동은 모두 선택 사항이며 어느 순서로 해도 됩니다. 활동을 마칠 때마다 그 준비가 자동으로 선택되고, 마지막으로 확인한 준비 하나만 다음 전투의 초반 효과가 됩니다.',
|
||||
'작전판에서 적 선봉의 움직임을 읽고, 보급 수레에서 방호구를 손보거나, 먼저 돌아온 동료와 지난 전투를 되짚을 수 있습니다. 세 활동은 모두 선택 사항이며 어느 순서로 해도 됩니다. 활동을 마치면 출진 보너스 후보가 열리며, 실제로 적용할 하나는 군영 장부에서 따로 확정합니다.',
|
||||
background: {
|
||||
textureKey: 'third-guangzong-sortie-camp-background',
|
||||
source: 'original-guangzong-forward-camp'
|
||||
|
||||
@@ -96,18 +96,18 @@ export const thirdCampPreparationPriorities: readonly
|
||||
},
|
||||
{
|
||||
id: 'equipment',
|
||||
label: '장비 정비',
|
||||
label: '선봉 방호',
|
||||
activityLabel: '승전 장비 확인',
|
||||
summary:
|
||||
'3차 승전 장비를 확인하면 첫 3턴 아군의 첫 피격 피해를 한 번 8% 줄입니다.',
|
||||
'수선한 방호구로 첫 3턴 아군의 첫 피격 피해를 한 번 8% 줄입니다.',
|
||||
deploymentLine:
|
||||
'장비 정비 · 아군 첫 피격 1회 방호 준비',
|
||||
'선봉 방호 · 아군 첫 피격 1회 방호 준비',
|
||||
activeLine:
|
||||
'장비 정비 · 첫 3턴 동안 아군 첫 피격 1회 피해 -8%',
|
||||
'선봉 방호 · 첫 3턴 동안 아군 첫 피격 1회 피해 -8%',
|
||||
appliedLine:
|
||||
'장비 정비 · 아군 첫 피격 방호 적용',
|
||||
'선봉 방호 · 아군 첫 피격 방호 적용',
|
||||
unusedLine:
|
||||
'장비 정비 · 방호가 발동하기 전에 준비 시간이 끝남',
|
||||
'선봉 방호 · 방호가 발동하기 전에 준비 시간이 끝남',
|
||||
effect: {
|
||||
kind: 'prepared-equipment-guard',
|
||||
activeThroughTurn: thirdCampPreparationActiveThroughTurn,
|
||||
@@ -216,13 +216,21 @@ export type ThirdCampPreparationBattlePresentation = {
|
||||
| 'active'
|
||||
| 'expired'
|
||||
| 'applied'
|
||||
| 'unused';
|
||||
| 'unused'
|
||||
| 'incompatible';
|
||||
text: string;
|
||||
activeThroughTurn: typeof thirdCampPreparationActiveThroughTurn;
|
||||
turnNumber: number | null;
|
||||
usageCount: number;
|
||||
};
|
||||
|
||||
export type ThirdCampPreparationCompatibility = {
|
||||
compatible: boolean;
|
||||
requiredUnitIds: string[];
|
||||
missingUnitIds: string[];
|
||||
disabledReason: 'required-units-missing' | null;
|
||||
};
|
||||
|
||||
export type ThirdCampPreparationCampaignState = {
|
||||
step?: unknown;
|
||||
latestBattleId?: unknown;
|
||||
@@ -236,6 +244,7 @@ export type ThirdCampPreparationCampaignState = {
|
||||
sourceBattleId?: unknown;
|
||||
targetBattleId?: unknown;
|
||||
priorityId?: unknown;
|
||||
confirmed?: unknown;
|
||||
} | null;
|
||||
acknowledgedVictoryRewardCategories?: Readonly<
|
||||
Record<string, readonly unknown[] | undefined>
|
||||
@@ -381,6 +390,7 @@ export function resolveThirdCampPreparationSelection(
|
||||
priorityId,
|
||||
label: availability.priority.label,
|
||||
selectable: availability.selectable,
|
||||
confirmed: selection.confirmed === true,
|
||||
...(availability.evidence
|
||||
? { evidence: cloneEvidence(availability.evidence) }
|
||||
: {}),
|
||||
@@ -391,17 +401,72 @@ export function resolveThirdCampPreparationSelection(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function shouldReviewThirdCampPreparationBeforeRetry(
|
||||
campaign?: ThirdCampPreparationCampaignState | null
|
||||
) {
|
||||
const selection =
|
||||
resolveThirdCampPreparationSelection(campaign);
|
||||
if (
|
||||
campaign?.step !== 'fourth-battle' ||
|
||||
!selection?.selectable ||
|
||||
selection.confirmed
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const targetSettlement =
|
||||
campaign.battleHistory?.[
|
||||
thirdCampPreparationTargetBattleId
|
||||
];
|
||||
return (
|
||||
targetSettlement?.outcome === 'defeat' ||
|
||||
(campaign.firstBattleReport?.battleId ===
|
||||
thirdCampPreparationTargetBattleId &&
|
||||
campaign.firstBattleReport.outcome === 'defeat')
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveThirdCampPreparationMemory(options: {
|
||||
campaign?: ThirdCampPreparationCampaignState | null;
|
||||
battleId?: string;
|
||||
}): ThirdCampPreparationMemory | undefined {
|
||||
return resolveThirdCampPreparationMemoryCandidate(options);
|
||||
}
|
||||
|
||||
export function resolveThirdCampPreparationBattleResumeMemory(
|
||||
options: {
|
||||
campaign?: ThirdCampPreparationCampaignState | null;
|
||||
battleId?: string;
|
||||
savedPriorityId?: unknown;
|
||||
}
|
||||
): ThirdCampPreparationMemory | undefined {
|
||||
if (!isThirdCampPreparationPriorityId(options.savedPriorityId)) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveThirdCampPreparationMemoryCandidate(
|
||||
options,
|
||||
options.savedPriorityId
|
||||
);
|
||||
}
|
||||
|
||||
function resolveThirdCampPreparationMemoryCandidate(
|
||||
options: {
|
||||
campaign?: ThirdCampPreparationCampaignState | null;
|
||||
battleId?: string;
|
||||
},
|
||||
resumedPriorityId?: ThirdCampPreparationPriorityId
|
||||
): ThirdCampPreparationMemory | undefined {
|
||||
if (options.battleId !== thirdCampPreparationTargetBattleId) {
|
||||
return undefined;
|
||||
}
|
||||
const selection = resolveThirdCampPreparationSelection(
|
||||
options.campaign
|
||||
);
|
||||
if (!selection?.selectable || !selection.evidence) {
|
||||
if (
|
||||
!selection?.selectable ||
|
||||
!selection.evidence ||
|
||||
(!selection.confirmed &&
|
||||
resumedPriorityId !== selection.priorityId)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const priority = getThirdCampPreparationPriority(
|
||||
@@ -436,6 +501,7 @@ export function resolveThirdCampPreparationBattlePresentation(options: {
|
||||
stage: 'deployment' | 'turn' | 'result';
|
||||
turnNumber?: number;
|
||||
usageCount?: number;
|
||||
compatibility?: ThirdCampPreparationCompatibility;
|
||||
}): ThirdCampPreparationBattlePresentation | undefined {
|
||||
const priority = getThirdCampPreparationPriority(
|
||||
options.memory.priorityId
|
||||
@@ -451,6 +517,15 @@ export function resolveThirdCampPreparationBattlePresentation(options: {
|
||||
|
||||
const usageCount = nonNegativeInteger(options.usageCount);
|
||||
if (options.stage === 'deployment') {
|
||||
if (options.compatibility?.compatible === false) {
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
'incompatible',
|
||||
`${options.memory.label} · 필요한 공명조가 편성되지 않아 이번 전투에는 미적용`,
|
||||
null,
|
||||
usageCount
|
||||
);
|
||||
}
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
'deployment',
|
||||
@@ -464,19 +539,42 @@ export function resolveThirdCampPreparationBattlePresentation(options: {
|
||||
if (!turnNumber) {
|
||||
return undefined;
|
||||
}
|
||||
const active =
|
||||
turnNumber <= thirdCampPreparationActiveThroughTurn;
|
||||
if (turnNumber > thirdCampPreparationActiveThroughTurn) {
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
'expired',
|
||||
`${options.memory.label} · 준비 효과 종료`,
|
||||
turnNumber,
|
||||
usageCount
|
||||
);
|
||||
}
|
||||
if (options.compatibility?.compatible === false) {
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
'incompatible',
|
||||
`${options.memory.label} · 필요한 공명조가 편성되지 않아 이번 전투에는 미적용`,
|
||||
turnNumber,
|
||||
usageCount
|
||||
);
|
||||
}
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
active ? 'active' : 'expired',
|
||||
active
|
||||
? `${options.memory.activeLine} · ${turnNumber}/${thirdCampPreparationActiveThroughTurn}턴`
|
||||
: `${options.memory.label} · 준비 효과 종료`,
|
||||
'active',
|
||||
`${options.memory.activeLine} · ${turnNumber}/${thirdCampPreparationActiveThroughTurn}턴`,
|
||||
turnNumber,
|
||||
usageCount
|
||||
);
|
||||
}
|
||||
|
||||
if (options.compatibility?.compatible === false) {
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
'incompatible',
|
||||
`${options.memory.label} · 필요한 공명조가 편성되지 않아 이번 전투에는 미적용`,
|
||||
null,
|
||||
usageCount
|
||||
);
|
||||
}
|
||||
const applied = usageCount > 0;
|
||||
return presentationBase(
|
||||
options.memory,
|
||||
@@ -489,6 +587,27 @@ export function resolveThirdCampPreparationBattlePresentation(options: {
|
||||
);
|
||||
}
|
||||
|
||||
export function evaluateThirdCampPreparationCompatibility(
|
||||
memory: ThirdCampPreparationMemory | undefined,
|
||||
selectedUnitIds: readonly string[]
|
||||
): ThirdCampPreparationCompatibility {
|
||||
const requiredUnitIds =
|
||||
memory?.effect.kind === 'return-bond-opening'
|
||||
? [...memory.effect.unitIds]
|
||||
: [];
|
||||
const selected = new Set(selectedUnitIds);
|
||||
const missingUnitIds = requiredUnitIds.filter(
|
||||
(unitId) => !selected.has(unitId)
|
||||
);
|
||||
return {
|
||||
compatible: missingUnitIds.length === 0,
|
||||
requiredUnitIds,
|
||||
missingUnitIds,
|
||||
disabledReason:
|
||||
missingUnitIds.length > 0 ? 'required-units-missing' : null
|
||||
};
|
||||
}
|
||||
|
||||
function selectThirdBattleVictoryRecord(
|
||||
campaign?: ThirdCampPreparationCampaignState | null
|
||||
): ThirdBattleRecordSelection | undefined {
|
||||
|
||||
@@ -154,13 +154,16 @@ import {
|
||||
type SecondBattleReliefMemory
|
||||
} from '../data/secondBattleReliefMemory';
|
||||
import {
|
||||
evaluateThirdCampPreparationCompatibility,
|
||||
resolveThirdCampPreparationBattlePresentation,
|
||||
resolveThirdCampPreparationBattleResumeMemory,
|
||||
resolveThirdCampPreparationMemory,
|
||||
thirdCampPreparationActiveThroughTurn,
|
||||
thirdCampPreparationSelectionRecordId,
|
||||
thirdCampPreparationSourceBattleId,
|
||||
thirdCampPreparationTargetBattleId,
|
||||
type ThirdCampPreparationBattlePresentation,
|
||||
type ThirdCampPreparationCompatibility,
|
||||
type ThirdCampPreparationMemory
|
||||
} from '../data/thirdCampPreparation';
|
||||
import {
|
||||
@@ -6551,8 +6554,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
const applied =
|
||||
this.thirdCampPreparationRuntime.usageCount > 0;
|
||||
const active = presentation.status === 'active';
|
||||
const incompatible = presentation.status === 'incompatible';
|
||||
const tone = applied
|
||||
? palette.green
|
||||
: incompatible
|
||||
? palette.red
|
||||
: active
|
||||
? palette.gold
|
||||
: 0x647485;
|
||||
@@ -6581,6 +6587,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
fontSize: this.battleUiFontSize(11),
|
||||
color: applied
|
||||
? '#a8ffd0'
|
||||
: incompatible
|
||||
? '#ffb6a6'
|
||||
: active
|
||||
? '#fff2b8'
|
||||
: '#9fb0bf',
|
||||
@@ -6789,6 +6797,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
if (!presentation) {
|
||||
return undefined;
|
||||
}
|
||||
if (presentation.status === 'incompatible') {
|
||||
return `준비 ${presentation.label} · 미편성 · 미발동`;
|
||||
}
|
||||
const applied =
|
||||
this.thirdCampPreparationRuntime.usageCount > 0;
|
||||
const active = presentation.status === 'active';
|
||||
@@ -9329,6 +9340,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
: `${this.cityEquipmentPreparationDisplayLine()} 준비 무장을 출전시키지 않아 이번 전투에는 반영되지 않습니다.`;
|
||||
}
|
||||
if (this.thirdCampPreparationMemory) {
|
||||
const compatibility =
|
||||
this.thirdCampPreparationSortieCompatibility();
|
||||
if (!compatibility.compatible) {
|
||||
return `${this.thirdCampPreparationMemory.label}은 필요한 공명조가 편성되지 않아 이번 전투에 반영되지 않습니다.`;
|
||||
}
|
||||
return `${this.thirdCampPreparationMemory.deploymentLine}. 효과는 3턴이 끝나면 종료됩니다.`;
|
||||
}
|
||||
if (this.secondBattleReliefMemory) {
|
||||
@@ -9404,7 +9420,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
const order = sortieOrderDefinition(this.launchSortieOrderId);
|
||||
return `선택 군령 · ${order.label} · ${order.summary}`;
|
||||
})();
|
||||
return `${this.thirdCampPreparationMemory.deploymentLine}\n${strategySummary}`;
|
||||
const compatibility =
|
||||
this.thirdCampPreparationSortieCompatibility();
|
||||
const preparationLine = compatibility.compatible
|
||||
? this.thirdCampPreparationMemory.deploymentLine
|
||||
: `${this.thirdCampPreparationMemory.label} · 공명조 미편성으로 이번 전투 미발동`;
|
||||
return `${preparationLine}\n${strategySummary}`;
|
||||
}
|
||||
if (this.launchSortieRecommendation) {
|
||||
return `선택 전략 · ${this.launchSortieRecommendation.label} · ${this.launchSortieRecommendation.summary}`;
|
||||
@@ -19668,9 +19689,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
};
|
||||
}
|
||||
|
||||
private thirdCampPreparationSortieCompatibility():
|
||||
ThirdCampPreparationCompatibility {
|
||||
return evaluateThirdCampPreparationCompatibility(
|
||||
this.thirdCampPreparationMemory,
|
||||
this.launchSortieUnitIds
|
||||
);
|
||||
}
|
||||
|
||||
private thirdCampPreparationActive() {
|
||||
return Boolean(
|
||||
this.thirdCampPreparationMemory &&
|
||||
this.thirdCampPreparationSortieCompatibility().compatible &&
|
||||
!this.battleOutcome &&
|
||||
this.turnNumber <= thirdCampPreparationActiveThroughTurn
|
||||
);
|
||||
@@ -19720,7 +19750,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
damageReduction:
|
||||
memory.effect.incomingDamageReductionPercent,
|
||||
labels: [
|
||||
`장비 정비 · 첫 피격 -${memory.effect.incomingDamageReductionPercent}%`
|
||||
`선봉 방호 · 첫 피격 -${memory.effect.incomingDamageReductionPercent}%`
|
||||
]
|
||||
};
|
||||
}
|
||||
@@ -19818,7 +19848,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
memory,
|
||||
stage,
|
||||
...(stage === 'turn' ? { turnNumber: this.turnNumber } : {}),
|
||||
usageCount: this.thirdCampPreparationRuntime.usageCount
|
||||
usageCount: this.thirdCampPreparationRuntime.usageCount,
|
||||
compatibility:
|
||||
this.thirdCampPreparationSortieCompatibility()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19900,7 +19932,10 @@ export class BattleScene extends Phaser.Scene {
|
||||
...(this.thirdCampPreparationMemory
|
||||
? [
|
||||
`군영 선택 · ${this.thirdCampPreparationMemory.label}`,
|
||||
this.thirdCampPreparationMemory.activeLine
|
||||
this.thirdCampPreparationSortieCompatibility()
|
||||
.compatible
|
||||
? this.thirdCampPreparationMemory.activeLine
|
||||
: `${this.thirdCampPreparationMemory.label} · 필요한 공명조 미편성으로 이번 전투 미발동`
|
||||
]
|
||||
: []),
|
||||
...cityInformationLines,
|
||||
@@ -21513,11 +21548,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
campaign: getCampaignState(),
|
||||
battleId: battleScenario.id
|
||||
});
|
||||
this.thirdCampPreparationMemory = resolveThirdCampPreparationMemory({
|
||||
campaign: getCampaignState(),
|
||||
battleId: battleScenario.id
|
||||
});
|
||||
const campaign = getCampaignState();
|
||||
this.thirdCampPreparationMemory =
|
||||
resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: battleScenario.id
|
||||
}) ??
|
||||
resolveThirdCampPreparationBattleResumeMemory({
|
||||
campaign,
|
||||
battleId: battleScenario.id,
|
||||
savedPriorityId:
|
||||
state.thirdCampPreparation?.priorityId
|
||||
});
|
||||
this.xuzhouStayBattlePayoff =
|
||||
resolveXuzhouStayBattlePayoff(
|
||||
campaign,
|
||||
@@ -31785,6 +31827,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
activeThroughTurn:
|
||||
thirdCampPreparationActiveThroughTurn,
|
||||
evidence: null,
|
||||
compatibility: null,
|
||||
trackedEnemyUnitId: null,
|
||||
bondId: null,
|
||||
unitIds: [],
|
||||
@@ -31837,6 +31880,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.thirdCampPreparationPresentation('result');
|
||||
const saveSnapshot =
|
||||
this.thirdCampPreparationSaveState();
|
||||
const compatibility =
|
||||
this.thirdCampPreparationSortieCompatibility();
|
||||
const mechanicsProbe = (() => {
|
||||
if (memory.effect.kind === 'marked-vanguard-intel') {
|
||||
const effect = memory.effect;
|
||||
@@ -31964,6 +32009,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
unitIds: [...memory.evidence.unitIds]
|
||||
}
|
||||
: { ...memory.evidence },
|
||||
compatibility: {
|
||||
compatible: compatibility.compatible,
|
||||
requiredUnitIds: [...compatibility.requiredUnitIds],
|
||||
missingUnitIds: [...compatibility.missingUnitIds],
|
||||
disabledReason: compatibility.disabledReason
|
||||
},
|
||||
trackedEnemyUnitId,
|
||||
bondId,
|
||||
unitIds,
|
||||
|
||||
@@ -55,8 +55,11 @@ import {
|
||||
thirdCampExplorationVisitId
|
||||
} from '../data/thirdCampExploration';
|
||||
import {
|
||||
evaluateThirdCampPreparationCompatibility,
|
||||
resolveThirdCampPreparationMemory,
|
||||
thirdCampPreparationSourceBattleId,
|
||||
thirdCampPreparationTargetBattleId,
|
||||
type ThirdCampPreparationEvidence,
|
||||
type ThirdCampPreparationPriorityId,
|
||||
type ThirdCampPreparationUnavailableReason
|
||||
} from '../data/thirdCampPreparation';
|
||||
@@ -11501,6 +11504,8 @@ export class CampScene extends Phaser.Scene {
|
||||
private thirdCampPreparationCloseButton?: Phaser.GameObjects.Rectangle;
|
||||
private thirdCampPreparationClearButton?: Phaser.GameObjects.Rectangle;
|
||||
private thirdCampPreparationCardViews: ThirdCampPreparationCardView[] = [];
|
||||
private thirdCampPreparationKeyboardIndex = 0;
|
||||
private thirdCampIncompatibleSortieConfirmSignature?: string;
|
||||
private visitedTabs = new Set<CampTab>();
|
||||
private selectedSortieUnitIds: string[] = [];
|
||||
private sortieFormationAssignments: SortieFormationAssignments = {};
|
||||
@@ -11554,6 +11559,8 @@ export class CampScene extends Phaser.Scene {
|
||||
init(data?: CampSceneData) {
|
||||
this.ownedCampTextureKeys.clear();
|
||||
this.thirdCampPreparationBrowserOpen = false;
|
||||
this.thirdCampPreparationKeyboardIndex = 0;
|
||||
this.thirdCampIncompatibleSortieConfirmSignature = undefined;
|
||||
if (data?.completedAftermathBattleId) {
|
||||
completeCampaignAftermath(data.completedAftermathBattleId);
|
||||
}
|
||||
@@ -11740,7 +11747,11 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
this.input.keyboard?.on('keydown-ESC', () => this.handleEscapeKey());
|
||||
this.input.keyboard?.on('keydown-ENTER', (event: KeyboardEvent) => this.handleSortieEnterKey(event));
|
||||
this.input.keyboard?.on('keydown', (event: KeyboardEvent) => this.handleSaveSlotKey(event));
|
||||
this.input.keyboard?.on('keydown', (event: KeyboardEvent) => {
|
||||
if (!this.handleThirdCampPreparationKey(event)) {
|
||||
this.handleSaveSlotKey(event);
|
||||
}
|
||||
});
|
||||
loadBattleUiIcons(this, () => this.ensureCampAssets(() => this.drawCampScene()));
|
||||
}
|
||||
|
||||
@@ -14147,6 +14158,98 @@ export class CampScene extends Phaser.Scene {
|
||||
this.startVictoryStory();
|
||||
}
|
||||
|
||||
private handleThirdCampPreparationKey(event: KeyboardEvent) {
|
||||
if (
|
||||
!this.thirdCampPreparationBrowserOpen ||
|
||||
this.sortiePrepStep !== 'briefing' ||
|
||||
this.sortieObjects.length === 0 ||
|
||||
this.navigationPending ||
|
||||
this.victoryRewardObjects.length > 0 ||
|
||||
this.equipmentSwapConfirmObjects.length > 0 ||
|
||||
this.saveSlotObjects.length > 0 ||
|
||||
this.saveSlotConfirmObjects.length > 0 ||
|
||||
this.reportFormationReviewVisible ||
|
||||
this.reportFormationHistoryVisible
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (event.repeat) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const progress = this.campaign
|
||||
? thirdCampPreparationProgress(this.campaign)
|
||||
: undefined;
|
||||
if (!progress) {
|
||||
return false;
|
||||
}
|
||||
const priorities = progress.availability;
|
||||
const moveFocus = (offset: number) => {
|
||||
this.thirdCampPreparationKeyboardIndex =
|
||||
(this.thirdCampPreparationKeyboardIndex +
|
||||
offset +
|
||||
priorities.length) %
|
||||
priorities.length;
|
||||
soundDirector.playSelect();
|
||||
this.showSortiePrep();
|
||||
};
|
||||
const chooseFocused = () => {
|
||||
const entry =
|
||||
priorities[this.thirdCampPreparationKeyboardIndex];
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
if (!entry.selectable) {
|
||||
this.openThirdCampPreparationActivity(
|
||||
entry.priority.id
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.selectThirdCampPreparationPriority(entry.priority.id);
|
||||
};
|
||||
|
||||
if (
|
||||
event.key === 'ArrowDown' ||
|
||||
event.key === 'ArrowRight' ||
|
||||
(event.key === 'Tab' && !event.shiftKey)
|
||||
) {
|
||||
event.preventDefault();
|
||||
moveFocus(1);
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
event.key === 'ArrowUp' ||
|
||||
event.key === 'ArrowLeft' ||
|
||||
(event.key === 'Tab' && event.shiftKey)
|
||||
) {
|
||||
event.preventDefault();
|
||||
moveFocus(-1);
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
event.key === 'Enter' ||
|
||||
event.key === ' ' ||
|
||||
event.code === 'Space'
|
||||
) {
|
||||
event.preventDefault();
|
||||
chooseFocused();
|
||||
return true;
|
||||
}
|
||||
if (/^[1-3]$/.test(event.key)) {
|
||||
event.preventDefault();
|
||||
this.thirdCampPreparationKeyboardIndex =
|
||||
Number(event.key) - 1;
|
||||
chooseFocused();
|
||||
return true;
|
||||
}
|
||||
if (event.key === '0') {
|
||||
event.preventDefault();
|
||||
this.clearThirdCampPreparationSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private addTabButton(label: string, tab: CampTab, x: number, y: number, width = 76, fontSize = 16) {
|
||||
const bg = this.scaleLegacyCampUi(this.add.rectangle(x, y, width, 34, 0x182431, 0.94));
|
||||
bg.setStrokeStyle(1, palette.blue, 0.62);
|
||||
@@ -14471,8 +14574,10 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private isThirdCampPreparationSlice(flow = this.currentSortieFlow()) {
|
||||
return (
|
||||
flow.afterBattleId === thirdCampPreparationSourceBattleId &&
|
||||
flow.nextBattleId === thirdCampPreparationTargetBattleId
|
||||
flow.nextBattleId === thirdCampPreparationTargetBattleId &&
|
||||
(flow.afterBattleId === thirdCampPreparationSourceBattleId ||
|
||||
this.retrySortieBattleId ===
|
||||
thirdCampPreparationTargetBattleId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14481,6 +14586,9 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private setSortiePrepStep(step: SortiePrepStep) {
|
||||
if (step !== 'briefing') {
|
||||
this.thirdCampPreparationBrowserOpen = false;
|
||||
}
|
||||
if (this.sortiePrepStep === step) {
|
||||
return;
|
||||
}
|
||||
@@ -14651,7 +14759,9 @@ export class CampScene extends Phaser.Scene {
|
||||
const commandTitle = thirdCampPreparation
|
||||
? thirdCampPreparation.ready
|
||||
? `출진 우선 · ${thirdCampPreparation.selectedLabel}`
|
||||
: '출진 우선 · 보너스 없음'
|
||||
: thirdCampPreparation.selectedLabel
|
||||
? `출진 우선 · ${thirdCampPreparation.selectedLabel} 확인 필요`
|
||||
: '출진 우선 · 보너스 없음'
|
||||
: firstBattleFollowup
|
||||
? `지난 전투 인계 · ${firstBattleFollowup.mentorName}`
|
||||
: '지휘관 판단';
|
||||
@@ -14659,9 +14769,22 @@ export class CampScene extends Phaser.Scene {
|
||||
const selectedPreparation = thirdCampPreparation?.availability.find(
|
||||
(entry) => entry.priority.id === thirdCampPreparation.selectedPriorityId
|
||||
);
|
||||
const preparationCompatibility =
|
||||
this.thirdCampPreparationSortieState();
|
||||
const preparationCompatibilityWarning =
|
||||
preparationCompatibility?.compatibility.compatible === false
|
||||
? `발동 불가 · ${preparationCompatibility.compatibility.missingUnitIds
|
||||
.map((unitId) => this.unitName(unitId))
|
||||
.join('·')} 편성 필요.`
|
||||
: '';
|
||||
const commandSummary = thirdCampPreparation
|
||||
? selectedPreparation?.priority.summary ??
|
||||
'야영지에서 완료한 활동은 보너스 하나로 선택할 수 있습니다. 선택하지 않고 출진해도 됩니다.'
|
||||
? [
|
||||
selectedPreparation?.priority.summary ??
|
||||
'야영지에서 완료한 활동은 보너스 하나로 선택할 수 있습니다. 선택하지 않고 출진해도 됩니다.',
|
||||
preparationCompatibilityWarning
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
: firstBattleFollowup
|
||||
? firstBattleFollowup.achieved
|
||||
? `${firstBattleFollowup.originalOrderLabel} 완수 → ${firstBattleFollowup.recommendedNextOrderLabel} · ${firstBattleFollowup.focusCriterionLabel}`
|
||||
@@ -14684,7 +14807,11 @@ export class CampScene extends Phaser.Scene {
|
||||
this.renderThirdCampPreparationToggle(
|
||||
x + width - 86,
|
||||
commandY + 51,
|
||||
thirdCampPreparation.ready ? '준비 변경' : '보너스 선택',
|
||||
thirdCampPreparation.ready
|
||||
? '준비 변경'
|
||||
: thirdCampPreparation.selectedPriorityId
|
||||
? '선택 확인'
|
||||
: '보너스 선택',
|
||||
depth + 2
|
||||
);
|
||||
} else if (firstBattleFollowup) {
|
||||
@@ -14733,6 +14860,15 @@ export class CampScene extends Phaser.Scene {
|
||||
button.setStrokeStyle(2, palette.gold, 0.92);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
const open = () => {
|
||||
const progress = this.campaign
|
||||
? thirdCampPreparationProgress(this.campaign)
|
||||
: undefined;
|
||||
const selectedIndex = progress?.availability.findIndex(
|
||||
(entry) =>
|
||||
entry.priority.id === progress.selectedPriorityId
|
||||
) ?? -1;
|
||||
this.thirdCampPreparationKeyboardIndex =
|
||||
selectedIndex >= 0 ? selectedIndex : 0;
|
||||
this.thirdCampPreparationBrowserOpen = true;
|
||||
soundDirector.playSelect();
|
||||
this.showSortiePrep();
|
||||
@@ -14777,8 +14913,8 @@ export class CampScene extends Phaser.Scene {
|
||||
this.add.text(
|
||||
x + 18,
|
||||
y + 41,
|
||||
'야영지에서 완료한 활동 중 하나만 첫 3턴에 반영합니다. 미선택 출진도 가능합니다.',
|
||||
this.textStyle(10, '#9fb0bf')
|
||||
'완료 후보 1개 확정 · ↑↓/Tab 이동 · Enter/Space 확정 · 1~3 바로 선택 · 0 해제',
|
||||
this.textStyle(9, '#9fb0bf')
|
||||
)
|
||||
).setDepth(depth + 1);
|
||||
|
||||
@@ -14830,20 +14966,35 @@ export class CampScene extends Phaser.Scene {
|
||||
progress.availability.forEach((entry, index) => {
|
||||
const priorityId = entry.priority.id;
|
||||
const selected = progress.selectedPriorityId === priorityId;
|
||||
const confirmed = selected && progress.confirmed;
|
||||
const focused =
|
||||
this.thirdCampPreparationKeyboardIndex === index;
|
||||
const requirementLine =
|
||||
this.thirdCampPreparationRequirementLine(entry.evidence);
|
||||
const cardY = y + 68 + index * 106;
|
||||
const cardHeight = 98;
|
||||
const fill = selected
|
||||
const fill = confirmed
|
||||
? 0x183126
|
||||
: selected
|
||||
? 0x352a17
|
||||
: entry.selectable
|
||||
? 0x17232e
|
||||
: 0x111820;
|
||||
const tone = selected ? palette.green : toneByPriority[priorityId];
|
||||
const tone = confirmed
|
||||
? palette.green
|
||||
: selected
|
||||
? palette.gold
|
||||
: toneByPriority[priorityId];
|
||||
const card = this.trackSortie(
|
||||
this.add.rectangle(x + 16, cardY, width - 32, cardHeight, fill, entry.selectable ? 0.98 : 0.78)
|
||||
);
|
||||
card.setOrigin(0);
|
||||
card.setDepth(depth + 1);
|
||||
card.setStrokeStyle(selected ? 2 : 1, tone, selected ? 0.96 : entry.selectable ? 0.68 : 0.28);
|
||||
card.setStrokeStyle(
|
||||
focused ? 3 : selected ? 2 : 1,
|
||||
focused ? 0xf4e3b5 : tone,
|
||||
focused ? 1 : selected ? 0.96 : entry.selectable ? 0.68 : 0.28
|
||||
);
|
||||
|
||||
const seal = this.trackSortie(
|
||||
this.add.circle(x + 43, cardY + 31, 17, tone, entry.selectable ? 0.9 : 0.38)
|
||||
@@ -14857,7 +15008,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.add.text(
|
||||
x + 68,
|
||||
cardY + 10,
|
||||
`${entry.priority.label} · ${entry.priority.activityLabel}`,
|
||||
`${focused ? '▶ ' : ''}${entry.priority.label} · ${entry.priority.activityLabel}`,
|
||||
this.textStyle(13, entry.selectable ? '#ffdf7b' : '#77818c', true)
|
||||
)
|
||||
).setDepth(depth + 2);
|
||||
@@ -14866,7 +15017,7 @@ export class CampScene extends Phaser.Scene {
|
||||
x + 68,
|
||||
cardY + 33,
|
||||
entry.selectable
|
||||
? entry.priority.summary
|
||||
? `${requirementLine} · ${entry.priority.summary}`
|
||||
: this.thirdCampPreparationUnavailableLabel(entry.unavailableReason),
|
||||
{
|
||||
...this.textStyle(10, entry.selectable ? '#d4dce6' : '#7f8994'),
|
||||
@@ -14885,7 +15036,11 @@ export class CampScene extends Phaser.Scene {
|
||||
actionY,
|
||||
82,
|
||||
32,
|
||||
selected ? 0x28533c : entry.selectable ? 0x4a371d : 0x1a2630,
|
||||
confirmed
|
||||
? 0x28533c
|
||||
: entry.selectable
|
||||
? 0x4a371d
|
||||
: 0x1a2630,
|
||||
entry.selectable ? 0.98 : 0.8
|
||||
)
|
||||
);
|
||||
@@ -14896,10 +15051,12 @@ export class CampScene extends Phaser.Scene {
|
||||
this.add.text(
|
||||
actionX,
|
||||
actionY,
|
||||
selected
|
||||
? '선택됨'
|
||||
confirmed
|
||||
? '확정됨'
|
||||
: selected
|
||||
? '확정'
|
||||
: entry.selectable
|
||||
? '선택'
|
||||
? '선택·확정'
|
||||
: this.thirdCampPreparationLockedActionLabel(priorityId),
|
||||
this.textStyle(10, entry.selectable ? '#fff2b8' : '#9fb0bf', true)
|
||||
)
|
||||
@@ -14924,7 +15081,11 @@ export class CampScene extends Phaser.Scene {
|
||||
);
|
||||
action.on('pointerout', () =>
|
||||
action.setFillStyle(
|
||||
selected ? 0x28533c : entry.selectable ? 0x4a371d : 0x1a2630,
|
||||
confirmed
|
||||
? 0x28533c
|
||||
: entry.selectable
|
||||
? 0x4a371d
|
||||
: 0x1a2630,
|
||||
entry.selectable ? 0.98 : 0.8
|
||||
)
|
||||
);
|
||||
@@ -14945,8 +15106,10 @@ export class CampScene extends Phaser.Scene {
|
||||
x + 18,
|
||||
y + height - 25,
|
||||
progress.ready
|
||||
? '선택 사항 · 해제하거나 다른 완료 활동으로 바꿀 수 있습니다.'
|
||||
: '선택 사항 · 보너스 없이 그대로 출진할 수 있습니다.',
|
||||
? '확정 완료 · 해제하거나 다른 완료 활동으로 바꿀 수 있습니다.'
|
||||
: progress.selectedPriorityId
|
||||
? '이전 자동 선택 · 적용하려면 위 카드에서 한 번 확정하세요.'
|
||||
: '선택 사항 · 보너스 없이 그대로 출진할 수 있습니다.',
|
||||
{
|
||||
...this.textStyle(
|
||||
10,
|
||||
@@ -14997,6 +15160,9 @@ export class CampScene extends Phaser.Scene {
|
||||
private thirdCampPreparationUnavailableLabel(
|
||||
reason?: ThirdCampPreparationUnavailableReason
|
||||
) {
|
||||
if (this.campaign?.step !== thirdBattleReturnCampStep) {
|
||||
return '첫 체류에서 완료하지 않은 활동입니다. 재도전 중에는 새 활동을 진행할 수 없습니다.';
|
||||
}
|
||||
if (reason === 'report-review-required') {
|
||||
return '야영지에서 추정과 작전판을 먼저 확인하면 선택할 수 있습니다.';
|
||||
}
|
||||
@@ -15009,10 +15175,57 @@ export class CampScene extends Phaser.Scene {
|
||||
return '광종 구원로 승리 기록이 있어야 선택할 수 있습니다.';
|
||||
}
|
||||
|
||||
private thirdCampPreparationSortieState() {
|
||||
if (!this.isThirdCampPreparationSlice()) {
|
||||
return undefined;
|
||||
}
|
||||
const campaign = this.campaign ?? getCampaignState();
|
||||
const memory = resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: thirdCampPreparationTargetBattleId
|
||||
});
|
||||
if (!memory) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
memory,
|
||||
compatibility: evaluateThirdCampPreparationCompatibility(
|
||||
memory,
|
||||
this.selectedSortieUnitIds
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
private thirdCampPreparationRequirementLine(
|
||||
evidence?: ThirdCampPreparationEvidence
|
||||
) {
|
||||
const requiredUnitIds =
|
||||
evidence && 'unitIds' in evidence ? evidence.unitIds : [];
|
||||
if (requiredUnitIds.length === 0) {
|
||||
return '발동 조건 · 편성 무관';
|
||||
}
|
||||
const selected = new Set(this.selectedSortieUnitIds);
|
||||
const selectedCount = requiredUnitIds.filter((unitId) =>
|
||||
selected.has(unitId)
|
||||
).length;
|
||||
return `발동 조건 · ${requiredUnitIds
|
||||
.map((unitId) => this.unitName(unitId))
|
||||
.join('·')} 동시 출전 ${selectedCount}/${requiredUnitIds.length}`;
|
||||
}
|
||||
|
||||
private thirdCampIncompatibleSortieSignature(
|
||||
priorityId: ThirdCampPreparationPriorityId,
|
||||
selectedUnitIds: readonly string[]
|
||||
) {
|
||||
return `${priorityId}|${[...selectedUnitIds].sort().join(',')}`;
|
||||
}
|
||||
|
||||
private thirdCampPreparationLockedActionLabel(
|
||||
_priorityId: ThirdCampPreparationPriorityId
|
||||
) {
|
||||
return '야영지로';
|
||||
return this.campaign?.step === thirdBattleReturnCampStep
|
||||
? '야영지로'
|
||||
: '초회 한정';
|
||||
}
|
||||
|
||||
private selectThirdCampPreparationPriority(
|
||||
@@ -15029,9 +15242,10 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
this.campaign = result.campaign;
|
||||
this.thirdCampPreparationBrowserOpen = true;
|
||||
this.thirdCampIncompatibleSortieConfirmSignature = undefined;
|
||||
soundDirector.playSelect();
|
||||
this.showCampNotice(
|
||||
`${result.priority.label} 선택 · ${result.memory.activeLine}`
|
||||
`${result.priority.label} 확정 · ${result.memory.activeLine}`
|
||||
);
|
||||
this.showSortiePrep();
|
||||
}
|
||||
@@ -15046,6 +15260,7 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
this.campaign = result.campaign;
|
||||
this.thirdCampPreparationBrowserOpen = true;
|
||||
this.thirdCampIncompatibleSortieConfirmSignature = undefined;
|
||||
soundDirector.playSelect();
|
||||
this.showCampNotice(
|
||||
result.changed
|
||||
@@ -15058,6 +15273,12 @@ export class CampScene extends Phaser.Scene {
|
||||
private openThirdCampPreparationActivity(
|
||||
_priorityId: ThirdCampPreparationPriorityId
|
||||
) {
|
||||
if (this.campaign?.step !== thirdBattleReturnCampStep) {
|
||||
this.showCampNotice(
|
||||
'재도전에서는 완료한 야영지 후보만 바꿀 수 있습니다. 새 활동은 광종 야영지에서만 진행할 수 있습니다.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
soundDirector.playSelect();
|
||||
this.thirdCampPreparationBrowserOpen = false;
|
||||
this.startCampNavigation('CampVisitExplorationScene', {
|
||||
@@ -16850,8 +17071,16 @@ export class CampScene extends Phaser.Scene {
|
||||
checklist: SortieChecklistItem[]
|
||||
) {
|
||||
const summary = this.sortieChecklistSummary(checklist);
|
||||
const items = this.firstSortieFinalCheckItems(checklist);
|
||||
const finalChecksComplete = items.every((item) => item.complete);
|
||||
const requiredItems = this.firstSortieFinalCheckItems(checklist);
|
||||
const preparationItem = checklist.find(
|
||||
(item) => item.label === '출진 우선 준비'
|
||||
);
|
||||
const items = preparationItem
|
||||
? [...requiredItems, preparationItem]
|
||||
: requiredItems;
|
||||
const finalChecksComplete = requiredItems.every(
|
||||
(item) => item.complete
|
||||
);
|
||||
const heading = finalChecksComplete ? '출진 준비 완료' : summary.requiredComplete ? '권장 점검 남음' : '필수 확인 필요';
|
||||
const headingColor = finalChecksComplete ? '#a8ffd0' : summary.requiredComplete ? '#ffdf7b' : '#ff9d7d';
|
||||
const headingTone = finalChecksComplete ? palette.green : summary.requiredComplete ? palette.gold : palette.red;
|
||||
@@ -16862,7 +17091,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.trackSortie(
|
||||
this.add.text(x + 18, y + 16, heading, this.textStyle(20, headingColor, true))
|
||||
).setDepth(depth + 1);
|
||||
this.trackSortie(this.add.text(x + 18, y + 45, `${items.filter((item) => item.complete).length}/${items.length} 핵심 조건`, this.textStyle(12, '#9fb0bf', true))).setDepth(depth + 1);
|
||||
this.trackSortie(this.add.text(x + 18, y + 45, `${requiredItems.filter((item) => item.complete).length}/${requiredItems.length} 핵심 조건${preparationItem ? ' · 야영지 효과 표시' : ''}`, this.textStyle(12, '#9fb0bf', true))).setDepth(depth + 1);
|
||||
|
||||
const cardGap = Math.min(67, Math.floor((height - 160) / Math.max(1, items.length)));
|
||||
const cardHeight = Math.max(42, cardGap - 8);
|
||||
@@ -16936,6 +17165,27 @@ export class CampScene extends Phaser.Scene {
|
||||
const pursuit = this.sortiePursuitPairs();
|
||||
const selectedOrder = this.currentSortieOrderDefinition();
|
||||
const cityEquipment = this.cityEquipmentSortieStatus();
|
||||
const thirdCampPreparation = this.isThirdCampPreparationSlice() &&
|
||||
this.campaign
|
||||
? thirdCampPreparationProgress(this.campaign)
|
||||
: undefined;
|
||||
const thirdCampSortie = this.thirdCampPreparationSortieState();
|
||||
const thirdCampLine = thirdCampPreparation
|
||||
? thirdCampPreparation.ready
|
||||
? thirdCampSortie?.compatibility.compatible === false
|
||||
? `야영지 ${thirdCampPreparation.selectedLabel} · 미발동 · ${thirdCampSortie.compatibility.missingUnitIds
|
||||
.map((unitId) => this.unitName(unitId))
|
||||
.join('·')} 편성 필요`
|
||||
: `야영지 ${thirdCampPreparation.selectedLabel} · 첫 3턴 · ${
|
||||
thirdCampSortie?.memory.effect.kind ===
|
||||
'return-bond-opening'
|
||||
? '공명조 편성 충족'
|
||||
: '편성 무관'
|
||||
}`
|
||||
: thirdCampPreparation.selectedPriorityId
|
||||
? `야영지 ${thirdCampPreparation.selectedLabel} · 확정 전 · 현재 미적용`
|
||||
: '야영지 보너스 없음 · 선택 없이 출진 가능'
|
||||
: undefined;
|
||||
const bg = this.trackSortie(this.add.rectangle(x, y, width, height, 0x0d141c, 0.78));
|
||||
bg.setOrigin(0);
|
||||
bg.setDepth(depth);
|
||||
@@ -16951,7 +17201,7 @@ export class CampScene extends Phaser.Scene {
|
||||
)
|
||||
).setDepth(depth + 1);
|
||||
this.renderSortieBattleUiIcon('focus', x + 22, y + 44, 22, depth + 1, 0.74);
|
||||
const secondaryLine = cityEquipment?.line ??
|
||||
const secondaryLine = cityEquipment?.line ?? thirdCampLine ??
|
||||
`${hasBattle ? `군령 ${selectedOrder?.label ?? '미선택'} · 추격 후보 ${pursuit.activePairs.length}조 · ` : ''}공명 ${synergy.activeBondCount} · 역할 ${synergy.coveredRoleCount}/3${hasBattle ? ` · 지형 ${synergy.terrainGrade}` : ''} · 대기 ${reserveUnits.length}명`;
|
||||
const secondaryText = this.trackSortie(
|
||||
this.add.text(
|
||||
@@ -16962,6 +17212,12 @@ export class CampScene extends Phaser.Scene {
|
||||
12,
|
||||
cityEquipment
|
||||
? cityEquipment.active ? '#a8ffd0' : '#ffdf7b'
|
||||
: thirdCampLine
|
||||
? thirdCampSortie?.compatibility.compatible === false
|
||||
? '#ff9d7d'
|
||||
: thirdCampPreparation?.ready
|
||||
? '#a8ffd0'
|
||||
: '#ffdf7b'
|
||||
: reserveUnits.length > 0 ? '#c8d2dd' : '#9fb0bf',
|
||||
true
|
||||
)
|
||||
@@ -16970,6 +17226,15 @@ export class CampScene extends Phaser.Scene {
|
||||
if (cityEquipment) {
|
||||
this.sortieCityEquipmentSummaryText = secondaryText;
|
||||
}
|
||||
if (thirdCampLine) {
|
||||
secondaryText.setInteractive({ useHandCursor: true });
|
||||
secondaryText.on('pointerdown', () => {
|
||||
this.sortiePrepStep = 'briefing';
|
||||
this.thirdCampPreparationBrowserOpen = true;
|
||||
soundDirector.playSelect();
|
||||
this.showSortiePrep();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private cityEquipmentSortieStatus() {
|
||||
@@ -20869,6 +21134,8 @@ export class CampScene extends Phaser.Scene {
|
||||
const thirdCampPreparation = this.isThirdCampPreparationSlice() && this.campaign
|
||||
? thirdCampPreparationProgress(this.campaign)
|
||||
: undefined;
|
||||
const thirdCampPreparationSortie =
|
||||
this.thirdCampPreparationSortieState();
|
||||
return [
|
||||
{
|
||||
label: requiredLabel,
|
||||
@@ -20879,10 +21146,24 @@ export class CampScene extends Phaser.Scene {
|
||||
...(thirdCampPreparation
|
||||
? [{
|
||||
label: '출진 우선 준비',
|
||||
complete: true,
|
||||
complete:
|
||||
thirdCampPreparationSortie?.compatibility.compatible ??
|
||||
true,
|
||||
detail: thirdCampPreparation.ready
|
||||
? `${thirdCampPreparation.selectedLabel} · 첫 3턴 반영`
|
||||
: '미선택 · 보너스 없이 출진 가능',
|
||||
? thirdCampPreparationSortie?.compatibility.compatible ===
|
||||
false
|
||||
? `${thirdCampPreparation.selectedLabel} · ${thirdCampPreparationSortie.compatibility.missingUnitIds
|
||||
.map((unitId) => this.unitName(unitId))
|
||||
.join('·')} 미편성 · 이번 전투 미발동`
|
||||
: `${thirdCampPreparation.selectedLabel} · 첫 3턴 반영 · ${
|
||||
thirdCampPreparationSortie?.memory.effect.kind ===
|
||||
'return-bond-opening'
|
||||
? '공명조 편성 충족'
|
||||
: '편성 무관'
|
||||
}`
|
||||
: thirdCampPreparation.selectedPriorityId
|
||||
? `${thirdCampPreparation.selectedLabel} · 이전 자동 선택 확인 필요 · 미적용`
|
||||
: '미선택 · 보너스 없이 출진 가능',
|
||||
priority: 'recommended' as const
|
||||
}]
|
||||
: []),
|
||||
@@ -21034,6 +21315,33 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
const thirdCampSortie =
|
||||
this.thirdCampPreparationSortieState();
|
||||
if (
|
||||
thirdCampSortie &&
|
||||
!thirdCampSortie.compatibility.compatible
|
||||
) {
|
||||
const signature =
|
||||
this.thirdCampIncompatibleSortieSignature(
|
||||
thirdCampSortie.memory.priorityId,
|
||||
this.selectedSortieUnitIds
|
||||
);
|
||||
if (
|
||||
this.thirdCampIncompatibleSortieConfirmSignature !==
|
||||
signature
|
||||
) {
|
||||
this.thirdCampIncompatibleSortieConfirmSignature =
|
||||
signature;
|
||||
this.showCampNotice(
|
||||
`${thirdCampSortie.memory.label} 미발동 경고 · ${thirdCampSortie.compatibility.missingUnitIds
|
||||
.map((unitId) => this.unitName(unitId))
|
||||
.join('·')}을 편성해야 합니다. 그대로 출진하려면 출진을 한 번 더 누르세요.`
|
||||
);
|
||||
this.showSortiePrep();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isFinalEpilogueFlow(flow) && this.campaign?.step === flow.campaignStep) {
|
||||
this.hideSortiePrep();
|
||||
this.startCampNavigation('EndingScene');
|
||||
@@ -21271,6 +21579,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.sortieCoreResonanceSelectorOpen = false;
|
||||
this.sortieCoreResonancePage = 0;
|
||||
this.thirdCampPreparationBrowserOpen = false;
|
||||
this.thirdCampIncompatibleSortieConfirmSignature = undefined;
|
||||
}
|
||||
this.sortieComparisonPanelView = undefined;
|
||||
this.sortiePursuitPanelView = undefined;
|
||||
@@ -26646,8 +26955,31 @@ export class CampScene extends Phaser.Scene {
|
||||
targetBattleId: thirdCampPreparation.targetBattleId,
|
||||
selectionRecordId: thirdCampPreparation.selectionRecordId,
|
||||
ready: thirdCampPreparation.ready,
|
||||
confirmed: thirdCampPreparation.confirmed,
|
||||
selectedPriorityId: thirdCampPreparation.selectedPriorityId,
|
||||
selectedLabel: thirdCampPreparation.selectedLabel,
|
||||
keyboardFocusIndex:
|
||||
this.thirdCampPreparationKeyboardIndex,
|
||||
keyboardFocusPriorityId:
|
||||
thirdCampPreparation.availability[
|
||||
this.thirdCampPreparationKeyboardIndex
|
||||
]?.priority.id ?? null,
|
||||
compatibility: (() => {
|
||||
const sortie = this.thirdCampPreparationSortieState();
|
||||
return sortie
|
||||
? {
|
||||
compatible: sortie.compatibility.compatible,
|
||||
requiredUnitIds: [
|
||||
...sortie.compatibility.requiredUnitIds
|
||||
],
|
||||
missingUnitIds: [
|
||||
...sortie.compatibility.missingUnitIds
|
||||
],
|
||||
disabledReason:
|
||||
sortie.compatibility.disabledReason
|
||||
}
|
||||
: null;
|
||||
})(),
|
||||
browserOpen: this.thirdCampPreparationBrowserOpen,
|
||||
toggleButtonBounds: this.sortieInteractiveObjectBoundsDebug(
|
||||
this.thirdCampPreparationToggleButton
|
||||
@@ -26695,8 +27027,12 @@ export class CampScene extends Phaser.Scene {
|
||||
targetBattleId: thirdCampPreparationTargetBattleId,
|
||||
selectionRecordId: null,
|
||||
ready: false,
|
||||
confirmed: false,
|
||||
selectedPriorityId: null,
|
||||
selectedLabel: null,
|
||||
keyboardFocusIndex: 0,
|
||||
keyboardFocusPriorityId: null,
|
||||
compatibility: null,
|
||||
browserOpen: false,
|
||||
toggleButtonBounds: null,
|
||||
closeButtonBounds: null,
|
||||
@@ -26777,6 +27113,8 @@ export class CampScene extends Phaser.Scene {
|
||||
thirdCampExploration?.completedActivityCount ?? 0,
|
||||
selectedPriorityId:
|
||||
thirdCampExploration?.selectedPriorityId ?? null,
|
||||
selectionConfirmed:
|
||||
thirdCampExploration?.selectionConfirmed ?? false,
|
||||
ready: thirdCampExploration?.ready ?? false,
|
||||
mode: !thirdCampExplorationVisit
|
||||
? 'unavailable'
|
||||
|
||||
@@ -749,6 +749,8 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
})) ?? [],
|
||||
selectedPriorityId:
|
||||
thirdProgress?.selectedPriorityId ?? null,
|
||||
selectionConfirmed:
|
||||
thirdProgress?.selectionConfirmed ?? false,
|
||||
selectedPriorityLabel:
|
||||
selectedThirdPriority?.label ?? null,
|
||||
hudActivityTitle:
|
||||
@@ -933,8 +935,9 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
this.completeThirdCampActivity(
|
||||
activityId as ThirdCampExplorationActivityId
|
||||
);
|
||||
return thirdCampExplorationProgress().selectedPriorityId ===
|
||||
activityId;
|
||||
return thirdCampExplorationProgress().completedActivityIds.includes(
|
||||
activityId as ThirdCampExplorationActivityId
|
||||
);
|
||||
}
|
||||
|
||||
private isSecondReliefVisit() {
|
||||
@@ -1061,7 +1064,7 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
? getThirdCampPreparationPriority(progress.selectedPriorityId)
|
||||
: undefined;
|
||||
return progress.completedActivityCount > 0
|
||||
? `선택 활동 ${progress.completedActivityCount} / 3 · 현재 우선 ${selected?.label ?? '미정'} · 언제든 출진 가능`
|
||||
? `준비 활동 ${progress.completedActivityCount} / 3 · 출진 보너스 ${selected?.label ?? '미선택'}${progress.ready ? '' : selected ? ' 확인 필요' : ''} · 군영 장부에서 확정`
|
||||
: '준비 활동 0 / 3 · 원하는 곳만 살피고 남쪽 출구에서 언제든 출진할 수 있습니다.';
|
||||
}
|
||||
|
||||
@@ -1721,7 +1724,7 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
150,
|
||||
'자유 준비 0 / 3',
|
||||
'작전판 · 보급 수레 · 먼저 귀환한 동료',
|
||||
'한 곳만 확인해도 방침이 정해지며, 마지막 활동이 현재 우선 기준이 됩니다.'
|
||||
'활동을 마치면 보너스 후보가 열립니다. 현재 출진 보너스는 자동으로 바뀌지 않습니다.'
|
||||
);
|
||||
this.objectiveCard = activityCard.background;
|
||||
this.thirdActivityTitleText = activityCard.titleText;
|
||||
@@ -1736,9 +1739,9 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
|
||||
const priorityCard = this.drawObjectiveCard(
|
||||
342,
|
||||
'현재 출진 우선',
|
||||
'출진 보너스 확정',
|
||||
'아직 정하지 않음',
|
||||
'완료한 활동을 다시 확인하면 보상 중복 없이 우선 기준만 바뀝니다.'
|
||||
'실제로 적용할 하나는 군영 장부에서 따로 선택하고 확정합니다.'
|
||||
);
|
||||
this.thirdPriorityLocationText = priorityCard.locationText;
|
||||
|
||||
@@ -2913,7 +2916,7 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
const newlyCompleted =
|
||||
!before.completedActivityIds.includes(activityId);
|
||||
this.inputReadyAt = this.time.now + inputCarryoverGuardMs;
|
||||
this.lastNotice = `${result.activity.shortLabel} · 현재 출진 우선`;
|
||||
this.lastNotice = `${result.activity.shortLabel} · 보너스 후보 해금`;
|
||||
if (newlyCompleted) {
|
||||
soundDirector.playObjectiveAchieved();
|
||||
} else {
|
||||
@@ -2925,8 +2928,8 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
this.refreshInteractionPrompt();
|
||||
this.showCompletionToast(
|
||||
newlyCompleted
|
||||
? result.activity.completionLine
|
||||
: `${result.priority.label}을 현재 출진 우선으로 다시 선택했습니다. 보상은 중복되지 않습니다.`,
|
||||
? `${result.activity.completionLine} 군영 장부에서 출진 보너스로 선택할 수 있습니다.`
|
||||
: `${result.priority.label} 후보는 이미 해금되어 있습니다. 현재 출진 보너스는 그대로 유지됩니다.`,
|
||||
'success',
|
||||
newlyCompleted ? 2100 : 1750
|
||||
);
|
||||
@@ -3572,7 +3575,7 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
|
||||
this.inputReadyAt = this.time.now + inputCarryoverGuardMs;
|
||||
this.closeChoicePanel();
|
||||
this.lastNotice = '동료 공명 · 현재 출진 우선';
|
||||
this.lastNotice = '동료 공명 · 보너스 후보 해금';
|
||||
if (alreadyCompleted) {
|
||||
soundDirector.playSelect();
|
||||
} else {
|
||||
@@ -3593,8 +3596,8 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
{
|
||||
speaker: '공명 기록',
|
||||
text: alreadyCompleted
|
||||
? `${result.memory.label}을 현재 출진 우선으로 다시 선택했습니다. 공명 보상은 중복되지 않습니다.`
|
||||
: `${result.choice.label} · 공명 +${result.rewardExp} · 다음 본영전 초반 협공 신호에 반영`
|
||||
? '동료 공명 후보는 이미 해금되어 있습니다. 현재 출진 보너스와 공명 보상은 바뀌지 않습니다.'
|
||||
: `${result.choice.label} · 공명 +${result.rewardExp} · 군영 장부에서 초반 협공 보너스로 선택 가능`
|
||||
}
|
||||
],
|
||||
() =>
|
||||
@@ -3657,7 +3660,7 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
portraitKey: 'liuBei',
|
||||
text:
|
||||
progress.completedActivityCount > 0
|
||||
? '살핀 준비와 현재 우선 기준은 장부에 남았다. 나머지는 선택이니 군영으로 돌아가 출진 여부를 정하자.'
|
||||
? '살핀 준비는 장부에 남았다. 실제로 적용할 보너스는 군영 장부에서 따로 정하고 출진하자.'
|
||||
: '아직 별도 준비를 확인하지 않았지만, 세 활동은 모두 선택이다. 군영 장부로 돌아가 바로 출진해도 된다.'
|
||||
},
|
||||
{
|
||||
@@ -3766,8 +3769,8 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
sceneHeight / 2 + 44,
|
||||
this.isThirdCampVisit()
|
||||
? this.visitComplete()
|
||||
? '선택한 출진 우선과 완료한 활동을 장부에 반영합니다.'
|
||||
: '선택 활동을 남겨 두고 군영 장부로 돌아갑니다.'
|
||||
? '완료한 활동을 장부에 반영하고, 출진 보너스는 군영에서 따로 확정합니다.'
|
||||
: '완료한 활동을 저장하고 보너스를 정할 군영 장부로 돌아갑니다.'
|
||||
: this.visitComplete()
|
||||
? this.isSecondReliefVisit()
|
||||
? '현장 수습과 광종 진군 방침을 출진 장부에 올립니다.'
|
||||
@@ -3826,21 +3829,27 @@ export class CampVisitExplorationScene extends Phaser.Scene {
|
||||
`자유 준비 ${progress.completedActivityCount} / 3`
|
||||
);
|
||||
this.thirdPriorityLocationText?.setText(
|
||||
selected?.label ?? '아직 정하지 않음'
|
||||
selected
|
||||
? `${selected.label}${progress.ready ? ' · 확정됨' : ' · 확인 필요'}`
|
||||
: '아직 정하지 않음'
|
||||
);
|
||||
const activityLines = progress.activities
|
||||
.map(
|
||||
(activity) =>
|
||||
`${activity.completed ? '✓' : '!'} ${activity.shortLabel}${
|
||||
activity.selected ? ' · 현재 우선' : ''
|
||||
activity.selected
|
||||
? progress.ready
|
||||
? ' · 출진 확정'
|
||||
: ' · 확인 필요'
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
.join('\n');
|
||||
this.progressText?.setText(
|
||||
`선택 활동 ${progress.completedActivityCount} / 3 · 모두 선택\n` +
|
||||
`현재 우선 · ${selected?.label ?? '아직 정하지 않음'}\n\n` +
|
||||
`준비 활동 ${progress.completedActivityCount} / 3 · 모두 선택\n` +
|
||||
`출진 보너스 · ${selected?.label ?? '미선택'}${selected && !progress.ready ? ' · 군영 확인 필요' : ''}\n\n` +
|
||||
`${activityLines}\n\n` +
|
||||
'완료한 곳을 다시 확인하면 보상 중복 없이 우선 기준만 바뀝니다.'
|
||||
'재방문해도 선택은 바뀌지 않습니다. 군영 장부에서 적용할 하나를 확정하세요.'
|
||||
);
|
||||
this.refreshThirdWorldFeedback();
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,10 @@ import {
|
||||
prologueDeparturePages,
|
||||
prologueOpeningPages
|
||||
} from '../data/prologueVillage';
|
||||
import {
|
||||
shouldReviewThirdCampPreparationBeforeRetry,
|
||||
thirdCampPreparationTargetBattleId
|
||||
} from '../data/thirdCampPreparation';
|
||||
import { battleIdForCampaignStep, isCampCampaignStep } from '../state/campaignRouting';
|
||||
import { readCampaignBattleResume, type CampaignBattleResume } from '../state/battleSaveStorage';
|
||||
import {
|
||||
@@ -1321,6 +1325,16 @@ export class TitleScene extends Phaser.Scene {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
shouldReviewThirdCampPreparationBeforeRetry(campaign)
|
||||
) {
|
||||
await this.navigateTo('CampScene', {
|
||||
openSortiePrep: true,
|
||||
retryBattleId: thirdCampPreparationTargetBattleId,
|
||||
skipIntroStory: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (campaign.pendingAftermathBattleId) {
|
||||
const { battleScenarios } = await import('../data/battles');
|
||||
const aftermathScenario = battleScenarios[campaign.pendingAftermathBattleId];
|
||||
|
||||
@@ -111,6 +111,7 @@ export type ThirdCampPreparationSelectionSnapshot = {
|
||||
sourceBattleId: typeof thirdCampPreparationSourceBattleId;
|
||||
targetBattleId: typeof thirdCampPreparationTargetBattleId;
|
||||
priorityId: ThirdCampPreparationPriorityId;
|
||||
confirmed: boolean;
|
||||
};
|
||||
|
||||
const campaignVictoryRewardCategoryIdSet = new Set<string>(campaignVictoryRewardCategoryIds);
|
||||
@@ -1190,6 +1191,13 @@ export function setFirstBattleReport(report: FirstBattleReport) {
|
||||
if (state.sortieRecommendationSelection?.battleId === battleId) {
|
||||
delete state.sortieRecommendationSelection;
|
||||
}
|
||||
if (
|
||||
reportClone.outcome === 'victory' &&
|
||||
battleId === thirdCampPreparationTargetBattleId &&
|
||||
state.thirdCampPreparationSelection?.targetBattleId === battleId
|
||||
) {
|
||||
delete state.thirdCampPreparationSelection;
|
||||
}
|
||||
state.latestBattleId = battleId;
|
||||
delete state.activeCityStayId;
|
||||
state.pendingAftermathBattleId = battleId as BattleScenarioId;
|
||||
@@ -2035,6 +2043,16 @@ function normalizeThirdCampPreparationSelection(
|
||||
value: unknown,
|
||||
campaign: CampaignState
|
||||
): ThirdCampPreparationSelectionSnapshot | undefined {
|
||||
const targetSettlement =
|
||||
campaign.battleHistory[thirdCampPreparationTargetBattleId];
|
||||
if (
|
||||
targetSettlement?.outcome === 'victory' ||
|
||||
(campaign.firstBattleReport?.battleId ===
|
||||
thirdCampPreparationTargetBattleId &&
|
||||
campaign.firstBattleReport.outcome === 'victory')
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
!isPlainObject(value) ||
|
||||
value.sourceBattleId !== thirdCampPreparationSourceBattleId ||
|
||||
@@ -2053,7 +2071,8 @@ function normalizeThirdCampPreparationSelection(
|
||||
? {
|
||||
sourceBattleId: thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: thirdCampPreparationTargetBattleId,
|
||||
priorityId: value.priorityId
|
||||
priorityId: value.priorityId,
|
||||
confirmed: value.confirmed === true
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,7 @@ import {
|
||||
import {
|
||||
getThirdCampPreparationPriority,
|
||||
hasThirdCampPreparationSourceVictory,
|
||||
resolveThirdCampPreparationMemory,
|
||||
resolveThirdCampPreparationSelection,
|
||||
thirdCampPreparationSourceBattleId,
|
||||
thirdCampPreparationTargetBattleId,
|
||||
type ThirdCampPreparationMemory,
|
||||
type ThirdCampPreparationPriorityDefinition
|
||||
} from '../data/thirdCampPreparation';
|
||||
import {
|
||||
@@ -55,7 +51,6 @@ export type ThirdCampExplorationActivityResult =
|
||||
ok: true;
|
||||
activity: ThirdCampExplorationActivityDefinition;
|
||||
priority: ThirdCampPreparationPriorityDefinition;
|
||||
memory: ThirdCampPreparationMemory;
|
||||
campaign: CampaignState;
|
||||
changed: boolean;
|
||||
}
|
||||
@@ -77,7 +72,6 @@ export type ThirdCampCompanionActivityResult =
|
||||
dialogue: ThirdCampCompanionDialogueDefinition;
|
||||
choice: ThirdCampCompanionDialogueChoice;
|
||||
rewardExp: number;
|
||||
memory: ThirdCampPreparationMemory;
|
||||
campaign: CampaignState;
|
||||
changed: boolean;
|
||||
}
|
||||
@@ -119,7 +113,10 @@ export function thirdCampExplorationProgress(
|
||||
selected: selection?.priorityId === activity.id
|
||||
})) ?? [],
|
||||
selectedPriorityId: selection?.priorityId ?? null,
|
||||
ready: selection?.selectable === true
|
||||
selectionConfirmed: selection?.confirmed === true,
|
||||
ready:
|
||||
selection?.selectable === true &&
|
||||
selection.confirmed === true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,30 +185,23 @@ export function recordThirdCampExplorationActivity(
|
||||
return { ok: false, reason: 'choice-required' };
|
||||
}
|
||||
|
||||
const persisted = persistActivitySelection(
|
||||
const persisted = persistActivityCompletion(
|
||||
snapshot,
|
||||
priorityId
|
||||
);
|
||||
if (!persisted) {
|
||||
return { ok: false, reason: 'save-unavailable' };
|
||||
}
|
||||
const memory = selectedMemory(persisted, priorityId);
|
||||
if (!memory) {
|
||||
restoreCampaignSnapshot(snapshot);
|
||||
return { ok: false, reason: 'save-unavailable' };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
activity,
|
||||
priority,
|
||||
memory,
|
||||
campaign: persisted,
|
||||
changed:
|
||||
!snapshot.completedCampVisits.includes(
|
||||
thirdCampExplorationActivityProgressId(priorityId)
|
||||
) ||
|
||||
snapshot.thirdCampPreparationSelection?.priorityId !== priorityId
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -258,7 +248,7 @@ export function completeThirdCampCompanionActivity(
|
||||
restoreCampaignSnapshot(snapshot);
|
||||
return { ok: false, reason: 'save-unavailable' };
|
||||
}
|
||||
const persisted = persistActivitySelection(
|
||||
const persisted = persistActivityCompletion(
|
||||
afterDialogue,
|
||||
'companion',
|
||||
snapshot
|
||||
@@ -266,11 +256,6 @@ export function completeThirdCampCompanionActivity(
|
||||
if (!persisted) {
|
||||
return { ok: false, reason: 'save-unavailable' };
|
||||
}
|
||||
const memory = selectedMemory(persisted, 'companion');
|
||||
if (!memory) {
|
||||
restoreCampaignSnapshot(snapshot);
|
||||
return { ok: false, reason: 'save-unavailable' };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -278,15 +263,12 @@ export function completeThirdCampCompanionActivity(
|
||||
dialogue,
|
||||
choice,
|
||||
rewardExp,
|
||||
memory,
|
||||
campaign: persisted,
|
||||
changed:
|
||||
!dialogueAlreadyCompleted ||
|
||||
!snapshot.completedCampVisits.includes(
|
||||
thirdCampExplorationActivityProgressId('companion')
|
||||
) ||
|
||||
snapshot.thirdCampPreparationSelection?.priorityId !==
|
||||
'companion'
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -325,7 +307,7 @@ function hasCompletedCompanionActivity(campaign: CampaignState) {
|
||||
);
|
||||
}
|
||||
|
||||
function persistActivitySelection(
|
||||
function persistActivityCompletion(
|
||||
current: CampaignState,
|
||||
activityId: ThirdCampExplorationActivityId,
|
||||
rollbackSnapshot: CampaignState = current
|
||||
@@ -339,12 +321,7 @@ function persistActivitySelection(
|
||||
updated = {
|
||||
...updated,
|
||||
acknowledgedVictoryRewardCategories:
|
||||
acknowledgedRewardCategoryForActivity(updated, activityId),
|
||||
thirdCampPreparationSelection: {
|
||||
sourceBattleId: thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: thirdCampPreparationTargetBattleId,
|
||||
priorityId: activityId
|
||||
}
|
||||
acknowledgedRewardCategoryForActivity(updated, activityId)
|
||||
};
|
||||
return persistCampaign(updated, rollbackSnapshot);
|
||||
}
|
||||
@@ -400,17 +377,6 @@ function withVisitMarkers(
|
||||
};
|
||||
}
|
||||
|
||||
function selectedMemory(
|
||||
campaign: CampaignState,
|
||||
activityId: ThirdCampExplorationActivityId
|
||||
) {
|
||||
const memory = resolveThirdCampPreparationMemory({
|
||||
campaign,
|
||||
battleId: thirdCampPreparationTargetBattleId
|
||||
});
|
||||
return memory?.priorityId === activityId ? memory : undefined;
|
||||
}
|
||||
|
||||
function applyCompanionBondReward(
|
||||
snapshot: CampaignState,
|
||||
dialogue: ThirdCampCompanionDialogueDefinition,
|
||||
|
||||
@@ -63,7 +63,10 @@ export function thirdCampPreparationProgress(
|
||||
availability,
|
||||
selectedPriorityId: selection?.priorityId ?? null,
|
||||
selectedLabel: selection?.label ?? null,
|
||||
ready: selection?.selectable === true
|
||||
confirmed: selection?.confirmed === true,
|
||||
ready:
|
||||
selection?.selectable === true &&
|
||||
selection.confirmed === true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,7 +97,9 @@ export function setThirdCampPreparationPriority(
|
||||
|
||||
const previousPriorityId =
|
||||
snapshot.thirdCampPreparationSelection?.priorityId;
|
||||
if (previousPriorityId === priorityId) {
|
||||
const previouslyConfirmed =
|
||||
snapshot.thirdCampPreparationSelection?.confirmed === true;
|
||||
if (previousPriorityId === priorityId && previouslyConfirmed) {
|
||||
const memory = resolveThirdCampPreparationMemory({
|
||||
campaign: snapshot,
|
||||
battleId: thirdCampPreparationTargetBattleId
|
||||
@@ -115,7 +120,8 @@ export function setThirdCampPreparationPriority(
|
||||
thirdCampPreparationSelection: {
|
||||
sourceBattleId: thirdCampPreparationSourceBattleId,
|
||||
targetBattleId: thirdCampPreparationTargetBattleId,
|
||||
priorityId
|
||||
priorityId,
|
||||
confirmed: true
|
||||
}
|
||||
};
|
||||
const persisted = persistSelection(updated, snapshot);
|
||||
@@ -173,9 +179,26 @@ export function clearThirdCampPreparationPriority():
|
||||
function campaignSupportsThirdCampPreparation(
|
||||
campaign: CampaignState
|
||||
) {
|
||||
return (
|
||||
const isInitialPreparation =
|
||||
campaign.step === 'third-camp' &&
|
||||
campaign.latestBattleId === thirdCampPreparationSourceBattleId &&
|
||||
campaign.latestBattleId ===
|
||||
thirdCampPreparationSourceBattleId;
|
||||
const targetSettlement =
|
||||
campaign.battleHistory[
|
||||
thirdCampPreparationTargetBattleId
|
||||
];
|
||||
const isTargetBattleRetry =
|
||||
campaign.step === 'fourth-battle' &&
|
||||
(
|
||||
targetSettlement?.outcome === 'defeat' ||
|
||||
(
|
||||
campaign.firstBattleReport?.battleId ===
|
||||
thirdCampPreparationTargetBattleId &&
|
||||
campaign.firstBattleReport.outcome === 'defeat'
|
||||
)
|
||||
);
|
||||
return (
|
||||
(isInitialPreparation || isTargetBattleRetry) &&
|
||||
hasThirdCampPreparationSourceVictory(campaign)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user