Polish sortie prep guidance

This commit is contained in:
2026-07-04 04:01:26 +09:00
parent d4e964a7ed
commit e7e0ad7dab

View File

@@ -183,6 +183,7 @@ type SortieChecklistItem = {
label: string; label: string;
complete: boolean; complete: boolean;
detail: string; detail: string;
priority?: 'required' | 'recommended';
}; };
type SortieUnitTacticalSummary = { type SortieUnitTacticalSummary = {
@@ -11265,9 +11266,10 @@ export class CampScene extends Phaser.Scene {
const y = Math.floor((this.scale.height - height) / 2); const y = Math.floor((this.scale.height - height) / 2);
const flow = this.currentSortieFlow(); const flow = this.currentSortieFlow();
const isBattleSortie = Boolean(flow.nextBattleId); const isBattleSortie = Boolean(flow.nextBattleId);
const checklist = this.sortieChecklist();
const prepTitle = isBattleSortie ? '출진 준비' : '군영 의정'; const prepTitle = isBattleSortie ? '출진 준비' : '군영 의정';
const prepSubtitle = isBattleSortie const prepSubtitle = isBattleSortie
? '대화, 보급, 상인 정비를 마친 뒤 함께 출전할 무장을 선택합니다.' ? this.sortiePrepSubtitle(checklist)
: '군영 대화와 장부를 정리한 뒤 다음 장면에 함께할 무장을 선택합니다.'; : '군영 대화와 장부를 정리한 뒤 다음 장면에 함께할 무장을 선택합니다.';
const shade = this.trackSortie(this.add.rectangle(0, 0, this.scale.width, this.scale.height, 0x020406, 0.68)); const shade = this.trackSortie(this.add.rectangle(0, 0, this.scale.width, this.scale.height, 0x020406, 0.68));
@@ -11283,13 +11285,13 @@ export class CampScene extends Phaser.Scene {
const title = this.trackSortie(this.add.text(x + 34, y + 28, prepTitle, this.textStyle(30, '#f2e3bf', true))); const title = this.trackSortie(this.add.text(x + 34, y + 28, prepTitle, this.textStyle(30, '#f2e3bf', true)));
title.setDepth(depth + 2); title.setDepth(depth + 2);
const subtitle = this.trackSortie( const subtitle = this.trackSortie(
this.add.text(x + 34, y + 70, prepSubtitle, this.textStyle(15, '#d4dce6')) this.add.text(x + 34, y + 70, this.compactText(prepSubtitle, 72), this.textStyle(15, '#d4dce6'))
); );
subtitle.setDepth(depth + 2); subtitle.setDepth(depth + 2);
this.renderSortieHeaderSummary(x + 690, y + 26, 464, 58, depth + 2); this.renderSortieHeaderSummary(x + 690, y + 26, 464, 58, depth + 2);
this.renderSortieBriefing(x + 34, y + 104, 560, 168, depth + 2); this.renderSortieBriefing(x + 34, y + 104, 560, 168, depth + 2);
this.renderSortieChecklist(x + 612, y + 104, 542, 168, depth + 2); this.renderSortieChecklist(x + 612, y + 104, 542, 168, depth + 2, checklist);
this.renderSortieUnitSummary(x + 34, y + 292, 390, 286, depth + 2); this.renderSortieUnitSummary(x + 34, y + 292, 390, 286, depth + 2);
this.renderSortieFormationPlan(x + 442, y + 292, 350, 286, depth + 2); this.renderSortieFormationPlan(x + 442, y + 292, 350, 286, depth + 2);
this.renderSortieFocusPanel(x + 810, y + 292, 344, 286, depth + 2); this.renderSortieFocusPanel(x + 810, y + 292, 344, 286, depth + 2);
@@ -11376,20 +11378,19 @@ export class CampScene extends Phaser.Scene {
this.trackSortie(this.add.text(x + 8, y + 18, this.compactText(value, 13), this.textStyle(10, '#f2e3bf', true))).setDepth(depth + 1); this.trackSortie(this.add.text(x + 8, y + 18, this.compactText(value, 13), this.textStyle(10, '#f2e3bf', true))).setDepth(depth + 1);
} }
private renderSortieChecklist(x: number, y: number, width: number, height: number, depth: number) { private renderSortieChecklist(x: number, y: number, width: number, height: number, depth: number, items = this.sortieChecklist()) {
const bg = this.trackSortie(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92)); const bg = this.trackSortie(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92));
bg.setOrigin(0); bg.setOrigin(0);
bg.setDepth(depth); bg.setDepth(depth);
bg.setStrokeStyle(1, palette.gold, 0.5); bg.setStrokeStyle(1, palette.gold, 0.5);
this.trackSortie(this.add.text(x + 18, y + 14, '준비 체크', this.textStyle(18, '#f2e3bf', true))).setDepth(depth + 1); this.trackSortie(this.add.text(x + 18, y + 14, '준비 체크', this.textStyle(18, '#f2e3bf', true))).setDepth(depth + 1);
const items = this.sortieChecklist();
const rowGap = Math.max(15, Math.floor((height - 50) / Math.max(1, items.length))); const rowGap = Math.max(15, Math.floor((height - 50) / Math.max(1, items.length)));
const compact = rowGap < 20; const compact = rowGap < 20;
items.forEach((item, index) => { items.forEach((item, index) => {
const rowY = y + 42 + index * rowGap; const rowY = y + 42 + index * rowGap;
const color = item.complete ? '#a8ffd0' : '#ffdf7b'; const color = item.complete ? '#a8ffd0' : item.priority === 'required' ? '#ff9d7d' : '#ffdf7b';
const status = item.complete ? '완료' : '주의'; const status = item.complete ? '완료' : item.priority === 'required' ? '필수' : '권장';
this.trackSortie(this.add.text(x + 18, rowY, status, this.textStyle(compact ? 10 : 13, color, true))).setDepth(depth + 1); this.trackSortie(this.add.text(x + 18, rowY, status, this.textStyle(compact ? 10 : 13, color, true))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 68, rowY, item.label, this.textStyle(compact ? 11 : 14, '#d4dce6', true))).setDepth(depth + 1); this.trackSortie(this.add.text(x + 68, rowY, item.label, this.textStyle(compact ? 11 : 14, '#d4dce6', true))).setDepth(depth + 1);
const detail = this.trackSortie(this.add.text(x + 184, rowY, this.compactText(item.detail, compact ? 36 : 42), this.textStyle(compact ? 10 : 12, item.complete ? '#9fb0bf' : '#d8b15f'))); const detail = this.trackSortie(this.add.text(x + 184, rowY, this.compactText(item.detail, compact ? 36 : 42), this.textStyle(compact ? 10 : 12, item.complete ? '#9fb0bf' : '#d8b15f')));
@@ -12524,6 +12525,18 @@ export class CampScene extends Phaser.Scene {
}; };
} }
private sortiePrepSubtitle(checklist: SortieChecklistItem[]) {
const nextRequired = checklist.find((item) => !item.complete && item.priority === 'required');
const nextRecommended = checklist.find((item) => !item.complete);
const next = nextRequired ?? nextRecommended;
if (!next) {
return '정비가 끝났습니다. 전열과 보급을 확인한 뒤 출진하십시오.';
}
const prefix = next.priority === 'required' ? '필수' : '권장';
return `${prefix}: ${next.label} - ${next.detail}`;
}
private currentSortieFlow() { private currentSortieFlow() {
return getSortieFlow(this.campaign?.latestBattleId, this.campaign?.step); return getSortieFlow(this.campaign?.latestBattleId, this.campaign?.step);
} }
@@ -12571,47 +12584,56 @@ export class CampScene extends Phaser.Scene {
{ {
label: requiredLabel, label: requiredLabel,
complete: requiredComplete, complete: requiredComplete,
detail: requiredDetail detail: requiredDetail,
priority: 'required'
}, },
{ {
label: '부상 장수 확인', label: '부상 장수 확인',
complete: injured.length === 0, complete: injured.length === 0,
detail: injured.length === 0 ? '전원 완전한 병력' : `${injured.map((unit) => unit.name).join(', ')} 회복 권장` detail: injured.length === 0 ? '전원 완전한 병력' : `${injured.map((unit) => unit.name).join(', ')} 회복 권장`,
priority: 'recommended'
}, },
{ {
label: '출전 구성', label: '출전 구성',
complete: requiredUnitIds.every((id) => selected.some((unit) => unit.id === id)) && selected.length > 0, complete: requiredUnitIds.every((id) => selected.some((unit) => unit.id === id)) && selected.length > 0,
detail: selected.length > 0 ? selected.map((unit) => unit.name).join(', ') : '출전 무장 선택 필요' detail: selected.length > 0 ? selected.map((unit) => unit.name).join(', ') : '출전 무장 선택 필요',
priority: 'required'
}, },
{ {
label: '추천 병종', label: '추천 병종',
complete: recommendedClassComplete, complete: recommendedClassComplete,
detail: recommendedClassLine detail: recommendedClassLine,
priority: 'recommended'
}, },
{ {
label: '전열 역할', label: '전열 역할',
complete: rolesReady, complete: rolesReady,
detail: `전열 ${roleCounts.front} · 돌파 ${roleCounts.flank} · 후원 ${roleCounts.support}` detail: `전열 ${roleCounts.front} · 돌파 ${roleCounts.flank} · 후원 ${roleCounts.support}`,
priority: 'recommended'
}, },
{ {
label: '전투 보급', label: '전투 보급',
complete: assignedSupplyCount > 0 || supplyCount > 0, complete: supplyCount === 0 || assignedSupplyCount > 0,
detail: supplyCount > 0 ? `배정 ${assignedSupplyCount}/${supplyCount}` : '정비 탭에서 보급 확인' detail: supplyCount > 0 ? `배정 ${assignedSupplyCount}/${supplyCount}` : '보급 없음',
priority: 'recommended'
}, },
{ {
label: '공명 대화', label: '공명 대화',
complete: availableDialogues.length === 0 || completedDialogues >= availableDialogues.length, complete: availableDialogues.length === 0 || completedDialogues >= availableDialogues.length,
detail: `${completedDialogues}/${availableDialogues.length} 완료` detail: `${completedDialogues}/${availableDialogues.length} 완료`,
priority: 'recommended'
}, },
{ {
label: '현지 방문', label: '현지 방문',
complete: availableVisits.length === 0 || completedVisits > 0, complete: availableVisits.length === 0 || completedVisits > 0,
detail: availableVisits.length === 0 ? '방문 없음' : `${completedVisits}/${availableVisits.length} 완료` detail: availableVisits.length === 0 ? '방문 없음' : `${completedVisits}/${availableVisits.length} 완료`,
priority: 'recommended'
}, },
{ {
label: '장비 상태', label: '장비 상태',
complete: this.visitedTabs.has('status'), complete: this.visitedTabs.has('status'),
detail: this.visitedTabs.has('status') ? '장수 탭 확인됨' : '장수 탭 확인 권장' detail: this.visitedTabs.has('status') ? '장수 탭 확인됨' : '장수 탭 확인 권장',
priority: 'recommended'
} }
]; ];
} }
@@ -14249,6 +14271,7 @@ export class CampScene extends Phaser.Scene {
} }
getDebugState() { getDebugState() {
const sortieChecklist = this.sortieChecklist();
return { return {
scene: this.scene.key, scene: this.scene.key,
activeTab: this.activeTab, activeTab: this.activeTab,
@@ -14262,6 +14285,8 @@ export class CampScene extends Phaser.Scene {
sortieDeploymentPreview: this.sortieDeploymentPreviewDebug(), sortieDeploymentPreview: this.sortieDeploymentPreviewDebug(),
sortieFocusedUnitId: this.sortieFocusedUnitId, sortieFocusedUnitId: this.sortieFocusedUnitId,
sortieFocusedUnit: this.sortieFocusedUnitSummary(), sortieFocusedUnit: this.sortieFocusedUnitSummary(),
sortieChecklist,
sortieNextAction: this.sortiePrepSubtitle(sortieChecklist),
reserveTrainingFocus: this.reserveTrainingFocusDefinition(), reserveTrainingFocus: this.reserveTrainingFocusDefinition(),
reserveTrainingFocusOptions: campaignReserveTrainingFocusDefinitions.map((focus) => ({ ...focus })), reserveTrainingFocusOptions: campaignReserveTrainingFocusDefinitions.map((focus) => ({ ...focus })),
sortieRosterView: this.sortieRosterViewState(this.sortieAllies().length), sortieRosterView: this.sortieRosterViewState(this.sortieAllies().length),