feat: prioritize third-victory return dialogue

This commit is contained in:
2026-07-27 11:35:35 +09:00
parent f07a45c62b
commit b2e7c8b403
7 changed files with 2018 additions and 17 deletions

View File

@@ -44,6 +44,11 @@ import {
secondBattleReliefSourceBattleId,
secondBattleReliefVisitId
} from '../data/secondBattleReliefExploration';
import {
resolveThirdBattlePriorityReturnDialogue,
thirdBattleReturnCampStep,
thirdBattleReturnSourceBattleId
} from '../data/thirdBattleReturnDialogue';
import {
findCityStayAfterBattle,
type CityEquipmentOffer,
@@ -11643,7 +11648,18 @@ export class CampScene extends Phaser.Scene {
this.selectedSortieUnitIds = this.normalizedSortieUnitIds(this.campaign.selectedSortieUnitIds);
this.sortieFormationAssignments = this.normalizedSortieFormationAssignments(this.campaign.sortieFormationAssignments);
this.sortieItemAssignments = this.normalizedSortieItemAssignments(this.campaign.sortieItemAssignments);
this.selectedDialogueId = this.availableCampDialogues()[0]?.id ?? campDialogues[0].id;
const availableCampDialogues = this.availableCampDialogues();
const priorityReturnDialogue = this.thirdBattlePriorityReturnDialogue();
const pendingPriorityReturnDialogueId =
priorityReturnDialogue &&
!this.completedCampDialogues().includes(priorityReturnDialogue.targetDialogueId) &&
availableCampDialogues.some((dialogue) => dialogue.id === priorityReturnDialogue.targetDialogueId)
? priorityReturnDialogue.targetDialogueId
: undefined;
this.selectedDialogueId =
pendingPriorityReturnDialogueId ??
availableCampDialogues[0]?.id ??
campDialogues[0].id;
this.selectedVisitId = this.availableCampVisits()[0]?.id ?? campVisits[0].id;
const cityStay = this.currentCityStay();
if (cityStay) {
@@ -12883,6 +12899,18 @@ export class CampScene extends Phaser.Scene {
return resolveFirstBattleCamaraderieMemory(this.campaign);
}
private thirdBattlePriorityReturnDialogue() {
if (
!this.campaign ||
this.retrySortieBattleId ||
this.campaign.step !== thirdBattleReturnCampStep ||
this.currentCampBattleId() !== thirdBattleReturnSourceBattleId
) {
return undefined;
}
return resolveThirdBattlePriorityReturnDialogue(this.campaign);
}
private firstPursuitScoutMemoryForNextSortie() {
const campaign = this.campaign;
const scenario = this.nextSortieScenario();
@@ -12917,6 +12945,18 @@ export class CampScene extends Phaser.Scene {
};
}
private campDialogueWithThirdBattlePriorityReturn(dialogue: CampDialogue) {
const priorityReturnDialogue = this.thirdBattlePriorityReturnDialogue();
if (!priorityReturnDialogue || dialogue.id !== priorityReturnDialogue.targetDialogueId) {
return dialogue;
}
return {
...dialogue,
title: priorityReturnDialogue.title,
lines: [...priorityReturnDialogue.lines]
};
}
private hasPendingFirstBattleCampFollowup() {
const followup = this.firstBattleCampFollowup();
return Boolean(
@@ -12933,6 +12973,14 @@ export class CampScene extends Phaser.Scene {
);
}
private hasPendingThirdBattlePriorityReturn() {
const priorityReturnDialogue = this.thirdBattlePriorityReturnDialogue();
return Boolean(
priorityReturnDialogue &&
!this.completedCampDialogues().includes(priorityReturnDialogue.targetDialogueId)
);
}
private availableCampDialogues(): CampDialogue[] {
const battleId = this.currentCampBattleId();
const battleDialogues = campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(battleId));
@@ -12940,7 +12988,9 @@ export class CampScene extends Phaser.Scene {
const available = dialogues.length > 0
? dialogues
: campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(defaultBattleScenario.id));
const resolved = available.map((dialogue) => this.campDialogueWithFirstBattleFollowup(dialogue));
const resolved = available
.map((dialogue) => this.campDialogueWithFirstBattleFollowup(dialogue))
.map((dialogue) => this.campDialogueWithThirdBattlePriorityReturn(dialogue));
const camaraderieDialogue = this.firstBattleCamaraderieDialogue();
return camaraderieDialogue && !resolved.some((dialogue) => dialogue.id === camaraderieDialogue.id)
? [...resolved, camaraderieDialogue]
@@ -13936,6 +13986,7 @@ export class CampScene extends Phaser.Scene {
const active = this.activeTab === tab;
const pendingFirstBattleFollowup = tab === 'dialogue' && this.hasPendingFirstBattleCampFollowup();
const pendingCamaraderieMemory = tab === 'dialogue' && this.hasPendingFirstBattleCamaraderieMemory();
const pendingThirdBattleReturn = tab === 'dialogue' && this.hasPendingThirdBattlePriorityReturn();
const pendingCampVisit = tab === 'visit' && this.availableCampVisits().some(
(visit) => !this.completedCampVisits().includes(visit.id)
);
@@ -13946,11 +13997,19 @@ export class CampScene extends Phaser.Scene {
indicator.setVisible(active || hovered);
indicator.setAlpha(active ? 1 : 0.66);
if (tab === 'dialogue' && newBadge) {
newBadge.setText(pendingFirstBattleFollowup ? '회고' : pendingCamaraderieMemory ? '전우' : '회고');
newBadge.setText(
pendingThirdBattleReturn
? '우선'
: pendingFirstBattleFollowup
? '회고'
: pendingCamaraderieMemory
? '전우'
: '회고'
);
}
newBadge?.setVisible(Boolean(
(tab === 'city' && this.hasPendingCityStayActions()) ||
(tab === 'dialogue' && (pendingFirstBattleFollowup || pendingCamaraderieMemory)) ||
(tab === 'dialogue' && (pendingThirdBattleReturn || pendingFirstBattleFollowup || pendingCamaraderieMemory)) ||
pendingCampVisit ||
(tab === 'supplies' && pendingCategories.has('supplies')) ||
(tab === 'equipment' && pendingCategories.has('equipment'))
@@ -19838,6 +19897,11 @@ export class CampScene extends Phaser.Scene {
bg.setStrokeStyle(1, palette.gold, 0.4);
const availableDialogues = this.availableCampDialogues();
const completedDialogues = this.completedAvailableDialogues().length;
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const priorityDialogueCompleted = Boolean(
thirdBattlePriorityReturn &&
this.completedCampDialogues().includes(thirdBattlePriorityReturn.targetDialogueId)
);
const availableVisits = this.availableCampVisits();
const completedVisits = this.completedAvailableVisits().length;
const selectedUnits = this.selectedSortieUnits();
@@ -19852,8 +19916,14 @@ export class CampScene extends Phaser.Scene {
const sortieNote = this.nextSortieRule().note;
const recoveryHint = this.sortieRecoveryHint();
const hasBattle = Boolean(this.nextSortieScenario());
const dialogueProgress = thirdBattlePriorityReturn
? `대화 우선 ${priorityDialogueCompleted ? 1 : 0}/1 · 선택 ${Math.max(
0,
completedDialogues - Number(priorityDialogueCompleted)
)}/${Math.max(0, availableDialogues.length - 1)}`
: `대화 ${completedDialogues}/${availableDialogues.length}`;
const readyLine = [
`대화 ${completedDialogues}/${availableDialogues.length}`,
dialogueProgress,
`방문 ${completedVisits}/${availableVisits.length}`,
`${hasBattle ? '출전' : '동행'} ${selectedSummary}`,
`보유 ${inventorySummary}`
@@ -20111,6 +20181,20 @@ export class CampScene extends Phaser.Scene {
const assignedSupplyCount = campSupplies.reduce((total, supply) => total + this.totalAssignedSupplyCount(supply.usableId), 0);
const availableDialogues = this.availableCampDialogues();
const completedDialogues = this.completedAvailableDialogues().length;
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const priorityDialogueCompleted = Boolean(
thirdBattlePriorityReturn &&
this.completedCampDialogues().includes(thirdBattlePriorityReturn.targetDialogueId)
);
const dialogueChecklistComplete = thirdBattlePriorityReturn
? priorityDialogueCompleted
: availableDialogues.length === 0 || completedDialogues >= availableDialogues.length;
const dialogueChecklistDetail = thirdBattlePriorityReturn
? `우선 ${priorityDialogueCompleted ? 1 : 0}/1 · 선택 ${Math.max(
0,
completedDialogues - Number(priorityDialogueCompleted)
)}/${Math.max(0, availableDialogues.length - 1)}`
: `${completedDialogues}/${availableDialogues.length} 완료`;
const availableVisits = this.availableCampVisits();
const completedVisits = this.completedAvailableVisits().length;
const selected = this.selectedSortieUnits();
@@ -20176,8 +20260,8 @@ export class CampScene extends Phaser.Scene {
},
{
label: '공명 대화',
complete: availableDialogues.length === 0 || completedDialogues >= availableDialogues.length,
detail: `${completedDialogues}/${availableDialogues.length} 완료`,
complete: dialogueChecklistComplete,
detail: dialogueChecklistDetail,
priority: 'recommended'
},
{
@@ -22740,6 +22824,7 @@ export class CampScene extends Phaser.Scene {
const dialogues = this.availableCampDialogues();
const firstBattleFollowup = this.firstBattleCampFollowup();
const camaraderieDialogue = this.firstBattleCamaraderieDialogue();
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const compactDialogueList = dialogues.length > 3;
const dialogueRowGap = compactDialogueList ? 35 : 64;
const dialogueRowHeight = compactDialogueList ? 30 : 48;
@@ -22764,12 +22849,22 @@ export class CampScene extends Phaser.Scene {
});
const isFirstBattleFollowup = firstBattleFollowup?.targetDialogueId === dialogue.id;
const isCamaraderieMemory = camaraderieDialogue?.id === dialogue.id;
const title = isFirstBattleFollowup || isCamaraderieMemory
const isThirdBattlePriorityReturn =
thirdBattlePriorityReturn?.targetDialogueId === dialogue.id;
const pendingThirdBattlePriorityReturn =
isThirdBattlePriorityReturn && !completed;
const title = isFirstBattleFollowup || isCamaraderieMemory || isThirdBattlePriorityReturn
? this.compactText(dialogue.title, compactDialogueList ? 20 : 14)
: dialogue.title;
this.track(this.add.text(x + 38, rowY + (compactDialogueList ? 3 : 8), title, this.textStyle(compactDialogueList ? 11 : 15, completed ? '#a8ffd0' : '#f2e3bf', true)));
if (isFirstBattleFollowup || isCamaraderieMemory) {
const badgeLabel = completed ? '완료' : isCamaraderieMemory ? '전우 기억' : '회고';
if (isFirstBattleFollowup || isCamaraderieMemory || pendingThirdBattlePriorityReturn) {
const badgeLabel = pendingThirdBattlePriorityReturn
? thirdBattlePriorityReturn?.priorityLabel ?? '새 이야기 · 우선'
: completed
? '완료'
: isCamaraderieMemory
? '전우 기억'
: '회고';
this.track(
this.add.text(
x + 320,
@@ -22777,7 +22872,13 @@ export class CampScene extends Phaser.Scene {
badgeLabel,
{
...this.textStyle(compactDialogueList ? 8 : 9, completed ? '#a8ffd0' : '#fff2b8', true),
backgroundColor: completed ? '#21402d' : isCamaraderieMemory ? '#36507a' : '#8a3f25',
backgroundColor: completed
? '#21402d'
: pendingThirdBattlePriorityReturn
? '#8a3f25'
: isCamaraderieMemory
? '#36507a'
: '#8a3f25',
padding: { x: 4, y: 2 }
}
)
@@ -22803,12 +22904,25 @@ export class CampScene extends Phaser.Scene {
return;
}
const completed = this.completedCampDialogues().includes(dialogue.id);
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const isThirdBattlePriorityReturn =
thirdBattlePriorityReturn?.targetDialogueId === dialogue.id;
const bg = this.track(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92));
bg.setOrigin(0);
bg.setStrokeStyle(1, completed ? palette.green : palette.gold, 0.5);
this.track(this.add.text(x + 18, y + 16, dialogue.title, this.textStyle(20, '#f2e3bf', true)));
if (isThirdBattlePriorityReturn) {
this.track(
this.add.text(
x + 18,
y + 45,
`전투 기록 · ${thirdBattlePriorityReturn.summary}`,
this.textStyle(11, completed ? '#a8ffd0' : '#ffdf7b', true)
)
);
}
const dialogueBody = this.track(
this.add.text(x + 18, y + 52, dialogue.lines.join('\n'), {
this.add.text(x + 18, y + (isThirdBattlePriorityReturn ? 70 : 52), dialogue.lines.join('\n'), {
...this.textStyle(14, '#d4dce6'),
wordWrap: { width: width - 36, useAdvancedWrap: true },
lineSpacing: 3
@@ -24152,11 +24266,14 @@ export class CampScene extends Phaser.Scene {
private showDialogueReward(dialogue: CampDialogue, choice: CampDialogueChoice, rewardExp: number) {
const followup = this.firstBattleCampFollowup();
const camaraderieMemory = this.firstBattleCamaraderieMemory();
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const preparationLine = camaraderieMemory?.dialogue.id === dialogue.id
? `\n다음 준비 · ${camaraderieMemory.unitNames.join('↔')}를 직접 핵심 공명조로 지정하면 추격 +${camaraderieMemory.bonusRate}%p · 자동 지정 없음`
: followup?.targetDialogueId === dialogue.id
? `\n다음 준비 · ${followup.recommendationTitle}`
: '';
: thirdBattlePriorityReturn?.targetDialogueId === dialogue.id
? '\n귀환 회고 완료 · 나머지 동료 대화는 자유 선택'
: '';
this.showCampNotice(`획득 내역 · ${choice.label} · 공명 +${rewardExp}\n응답 · ${choice.response}${preparationLine}`);
}
@@ -25155,6 +25272,7 @@ export class CampScene extends Phaser.Scene {
const firstBattleCampFollowup = this.firstBattleCampFollowup();
const firstBattleCamaraderieMemory = this.firstBattleCamaraderieMemory();
const firstBattleCamaraderieDialogue = this.firstBattleCamaraderieDialogue();
const thirdBattlePriorityReturn = this.thirdBattlePriorityReturnDialogue();
const firstPursuitScoutVisit = this.availableCampVisits().find(
(visit) => visit.id === firstPursuitScoutVisitId
);
@@ -25326,6 +25444,32 @@ export class CampScene extends Phaser.Scene {
}
}
: null,
thirdBattlePriorityReturn: thirdBattlePriorityReturn
? {
available: true,
sourceBattleId: thirdBattlePriorityReturn.sourceBattleId,
sourceTurn: thirdBattlePriorityReturn.turnNumber,
reportSource: thirdBattlePriorityReturn.reportSource,
fortAchieved: thirdBattlePriorityReturn.fortAchieved,
quickAchieved: thirdBattlePriorityReturn.quickAchieved,
variant: thirdBattlePriorityReturn.variantId,
targetDialogueId: thirdBattlePriorityReturn.targetDialogueId,
priorityLabel: thirdBattlePriorityReturn.priorityLabel,
summary: thirdBattlePriorityReturn.summary,
dialogueTitle: thirdBattlePriorityReturn.title,
dialogueLines: [...thirdBattlePriorityReturn.lines],
selected: this.selectedDialogueId === thirdBattlePriorityReturn.targetDialogueId,
completed: this.completedCampDialogues().includes(
thirdBattlePriorityReturn.targetDialogueId
),
pending: !this.completedCampDialogues().includes(
thirdBattlePriorityReturn.targetDialogueId
),
rowBounds: this.sortieObjectBoundsDebug(
this.campDialogueRowButtons[thirdBattlePriorityReturn.targetDialogueId]
)
}
: null,
firstPursuitScoutMemory: {
visitId: firstPursuitScoutVisitId,
available: Boolean(firstPursuitScoutVisit),
@@ -25396,6 +25540,8 @@ export class CampScene extends Phaser.Scene {
completed: this.completedCampDialogues().includes(selectedCampDialogue.id),
firstVictoryFollowup: firstBattleCampFollowup?.targetDialogueId === selectedCampDialogue.id,
camaraderieMemory: firstBattleCamaraderieMemory?.dialogue.id === selectedCampDialogue.id,
thirdBattlePriorityReturn:
thirdBattlePriorityReturn?.targetDialogueId === selectedCampDialogue.id,
rowBounds: this.sortieObjectBoundsDebug(this.campDialogueRowButtons[selectedCampDialogue.id]),
bodyBounds: this.sortieTextBoundsDebug(this.campDialogueBodyText),
choices: selectedCampDialogue.choices.map((choice) => ({