feat: carry first-battle camaraderie into sortie prep
This commit is contained in:
@@ -28,6 +28,12 @@ import {
|
||||
resolveFirstBattleCampFollowup,
|
||||
type FirstBattleCampFollowup
|
||||
} from '../data/firstBattleCampFollowup';
|
||||
import {
|
||||
firstBattleCamaraderieBonusFor,
|
||||
firstBattleCamaraderieCoreRateBonus,
|
||||
resolveFirstBattleCamaraderieMemory,
|
||||
type FirstBattleCamaraderieMemory
|
||||
} from '../data/firstBattleCamaraderieMemory';
|
||||
import {
|
||||
findCityStayAfterBattle,
|
||||
type CityEquipmentOffer,
|
||||
@@ -605,6 +611,10 @@ type SortiePursuitPanelView = {
|
||||
rows: {
|
||||
bondId: string;
|
||||
level: number;
|
||||
baseChainRate: number;
|
||||
bonusChainRate: number;
|
||||
effectiveChainRate: number;
|
||||
camaraderieRemembered: boolean;
|
||||
chainRate: number;
|
||||
selected: boolean;
|
||||
state: 'selected' | 'candidate';
|
||||
@@ -615,7 +625,10 @@ type SortiePursuitPanelView = {
|
||||
};
|
||||
|
||||
type SortieCoreResonanceCandidate = SortieSynergyActiveBond & {
|
||||
baseCoreChainRate: number;
|
||||
camaraderieBonusRate: number;
|
||||
coreChainRate: number;
|
||||
camaraderieRemembered: boolean;
|
||||
selected: boolean;
|
||||
};
|
||||
|
||||
@@ -12807,6 +12820,24 @@ export class CampScene extends Phaser.Scene {
|
||||
return resolution.source === 'campaign' ? resolution : undefined;
|
||||
}
|
||||
|
||||
private firstBattleCamaraderieMemory(): FirstBattleCamaraderieMemory | undefined {
|
||||
return resolveFirstBattleCamaraderieMemory(this.campaign);
|
||||
}
|
||||
|
||||
private firstBattleCamaraderieDialogue(): CampDialogue | undefined {
|
||||
if (!this.isFirstSortiePrepSlice()) {
|
||||
return undefined;
|
||||
}
|
||||
const memory = this.firstBattleCamaraderieMemory();
|
||||
if (!memory) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...memory.dialogue,
|
||||
availableAfterBattleIds: [memory.sourceBattleId]
|
||||
};
|
||||
}
|
||||
|
||||
private campDialogueWithFirstBattleFollowup(dialogue: CampDialogue) {
|
||||
const followup = this.firstBattleCampFollowup();
|
||||
if (!followup || dialogue.id !== followup.targetDialogueId) {
|
||||
@@ -12827,14 +12858,26 @@ export class CampScene extends Phaser.Scene {
|
||||
);
|
||||
}
|
||||
|
||||
private availableCampDialogues() {
|
||||
private hasPendingFirstBattleCamaraderieMemory() {
|
||||
const dialogue = this.firstBattleCamaraderieDialogue();
|
||||
return Boolean(
|
||||
dialogue &&
|
||||
!this.completedCampDialogues().includes(dialogue.id)
|
||||
);
|
||||
}
|
||||
|
||||
private availableCampDialogues(): CampDialogue[] {
|
||||
const battleId = this.currentCampBattleId();
|
||||
const battleDialogues = campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(battleId));
|
||||
const dialogues = this.filterCampEventsByStep(battleDialogues);
|
||||
const available = dialogues.length > 0
|
||||
? dialogues
|
||||
: campDialogues.filter((dialogue) => dialogue.availableAfterBattleIds.includes(defaultBattleScenario.id));
|
||||
return available.map((dialogue) => this.campDialogueWithFirstBattleFollowup(dialogue));
|
||||
const resolved = available.map((dialogue) => this.campDialogueWithFirstBattleFollowup(dialogue));
|
||||
const camaraderieDialogue = this.firstBattleCamaraderieDialogue();
|
||||
return camaraderieDialogue && !resolved.some((dialogue) => dialogue.id === camaraderieDialogue.id)
|
||||
? [...resolved, camaraderieDialogue]
|
||||
: resolved;
|
||||
}
|
||||
|
||||
private completedAvailableDialogues() {
|
||||
@@ -13824,15 +13867,20 @@ export class CampScene extends Phaser.Scene {
|
||||
const pendingCategories = new Set(getPendingCampaignVictoryRewardCategories());
|
||||
this.tabButtons.forEach(({ tab, bg, text, indicator, newBadge, hovered }) => {
|
||||
const active = this.activeTab === tab;
|
||||
const pendingFirstBattleFollowup = tab === 'dialogue' && this.hasPendingFirstBattleCampFollowup();
|
||||
const pendingCamaraderieMemory = tab === 'dialogue' && this.hasPendingFirstBattleCamaraderieMemory();
|
||||
bg.setFillStyle(active ? 0x2b4052 : hovered ? 0x243747 : 0x182431, active || hovered ? 0.98 : 0.94);
|
||||
bg.setStrokeStyle(active || hovered ? 2 : 1, active || hovered ? accentColor : palette.blue, active ? 0.96 : hovered ? 0.82 : 0.62);
|
||||
text.setColor(active || hovered ? '#fff2b8' : '#f2e3bf');
|
||||
indicator.setFillStyle(accentColor, 1);
|
||||
indicator.setVisible(active || hovered);
|
||||
indicator.setAlpha(active ? 1 : 0.66);
|
||||
if (tab === 'dialogue' && newBadge) {
|
||||
newBadge.setText(pendingFirstBattleFollowup ? '회고' : pendingCamaraderieMemory ? '전우' : '회고');
|
||||
}
|
||||
newBadge?.setVisible(Boolean(
|
||||
(tab === 'city' && this.hasPendingCityStayActions()) ||
|
||||
(tab === 'dialogue' && this.hasPendingFirstBattleCampFollowup()) ||
|
||||
(tab === 'dialogue' && (pendingFirstBattleFollowup || pendingCamaraderieMemory)) ||
|
||||
(tab === 'supplies' && pendingCategories.has('supplies')) ||
|
||||
(tab === 'equipment' && pendingCategories.has('equipment'))
|
||||
));
|
||||
@@ -15194,11 +15242,15 @@ export class CampScene extends Phaser.Scene {
|
||||
this.setCampaignSortieCoreSelectorVisible(true);
|
||||
view.background.setStrokeStyle(selectedCore ? 2 : 1, selectedCore ? palette.green : palette.gold, selectedCore ? 0.78 : 0.62);
|
||||
view.title.setText('핵심 공명조');
|
||||
view.summary.setText(selectedCore ? `지정 ${selectedCore.coreChainRate}% · 후보 ${coreCandidates.length}조` : `미지정 · 후보 ${coreCandidates.length}조`);
|
||||
view.summary.setText(
|
||||
selectedCore
|
||||
? `지정 ${selectedCore.coreChainRate}%${selectedCore.camaraderieRemembered ? ` · 기억 +${selectedCore.camaraderieBonusRate}%p` : ''} · 후보 ${coreCandidates.length}조`
|
||||
: `미지정 · 후보 ${coreCandidates.length}조`
|
||||
);
|
||||
view.headline
|
||||
.setText(
|
||||
selectedCore
|
||||
? `${selectedCore.unitNames[0]}↔${selectedCore.unitNames[1]} · Lv${selectedCore.level} 핵심 추격 준비`
|
||||
? `${selectedCore.unitNames[0]}↔${selectedCore.unitNames[1]} · Lv${selectedCore.level} 핵심 추격 ${selectedCore.baseCoreChainRate}%${selectedCore.camaraderieRemembered ? ` + 전우 기억 ${selectedCore.camaraderieBonusRate}%p` : ''}`
|
||||
: coreCandidates.length > 0
|
||||
? '아래 인연 칩을 눌러 이번 전투의 핵심 한 조를 정하십시오.'
|
||||
: `Lv${coreSortieResonanceMinLevel} 이상 인연 조원을 함께 편성하면 후보가 열립니다.`
|
||||
@@ -15208,7 +15260,11 @@ export class CampScene extends Phaser.Scene {
|
||||
.setText(this.compactText(this.sortiePlanFeedback || (selectedCore ? '다시 누르면 지정을 해제합니다.' : '정확히 한 조만 지정되며 다른 조를 누르면 즉시 교체됩니다.'), 68))
|
||||
.setColor(this.sortiePlanFeedback || selectedCore ? '#a8ffd0' : '#d4dce6');
|
||||
view.detail
|
||||
.setText(`Lv${coreSortieResonanceMinLevel}+ · 핵심 추격 8/18/28% · 일반 추격 확률은 그대로 유지`)
|
||||
.setText(
|
||||
coreCandidates.some((candidate) => candidate.camaraderieRemembered)
|
||||
? `전우 기억한 같은 조 직접 지정 시 +${firstBattleCamaraderieCoreRateBonus}%p · 재클릭 해제 · 일반 추격 유지`
|
||||
: `Lv${coreSortieResonanceMinLevel}+ · 핵심 추격 8/18/28% · 일반 추격 확률은 그대로 유지`
|
||||
)
|
||||
.setColor('#9fb0bf');
|
||||
return;
|
||||
}
|
||||
@@ -15865,10 +15921,16 @@ export class CampScene extends Phaser.Scene {
|
||||
const rows: SortiePursuitPanelView['rows'] = visibleCandidates.map((candidate, index) => {
|
||||
const chipX = x + index * (chipWidth + chipGap);
|
||||
const selected = candidate.selected;
|
||||
const chipText = `${selected ? '핵심' : '지정'} ${candidate.coreChainRate}% ${candidate.unitNames[0]}↔${candidate.unitNames[1]}`;
|
||||
const rateText = candidate.camaraderieRemembered
|
||||
? `${candidate.baseCoreChainRate}→${candidate.coreChainRate}%`
|
||||
: `${candidate.coreChainRate}%`;
|
||||
const chipText = `${selected ? '핵심' : '지정'} ${rateText} · ${candidate.unitNames[0]}↔${candidate.unitNames[1]}`;
|
||||
const chipLabel = candidate.camaraderieRemembered
|
||||
? `${this.compactText(chipText, 20)}\n전우 기억 +${candidate.camaraderieBonusRate}%p`
|
||||
: this.compactText(chipText, chipWidth >= 140 ? 18 : 15);
|
||||
const fill = selected ? 0x493719 : 0x173329;
|
||||
const stroke = selected ? palette.gold : palette.green;
|
||||
const background = this.trackSortie(this.add.rectangle(chipX, y, chipWidth, 22, fill, 0.98));
|
||||
const background = this.trackSortie(this.add.rectangle(chipX, y, chipWidth, 32, fill, 0.98));
|
||||
background.setOrigin(0);
|
||||
background.setDepth(depth);
|
||||
background.setStrokeStyle(selected ? 2 : 1, stroke, selected ? 0.96 : 0.68);
|
||||
@@ -15883,9 +15945,13 @@ export class CampScene extends Phaser.Scene {
|
||||
const label = this.trackSortie(
|
||||
this.add.text(
|
||||
chipX + chipWidth / 2,
|
||||
y + 11,
|
||||
this.compactText(chipText, chipWidth >= 140 ? 18 : 15),
|
||||
this.textStyle(9, selected ? '#ffdf7b' : '#a8ffd0', true)
|
||||
y + 16,
|
||||
chipLabel,
|
||||
{
|
||||
...this.textStyle(candidate.camaraderieRemembered ? 8 : 9, selected ? '#ffdf7b' : '#a8ffd0', true),
|
||||
align: 'center',
|
||||
lineSpacing: 0
|
||||
}
|
||||
)
|
||||
);
|
||||
label.setOrigin(0.5);
|
||||
@@ -15893,6 +15959,10 @@ export class CampScene extends Phaser.Scene {
|
||||
return {
|
||||
bondId: candidate.id,
|
||||
level: candidate.level,
|
||||
baseChainRate: candidate.baseCoreChainRate,
|
||||
bonusChainRate: candidate.camaraderieBonusRate,
|
||||
effectiveChainRate: candidate.coreChainRate,
|
||||
camaraderieRemembered: candidate.camaraderieRemembered,
|
||||
chainRate: candidate.coreChainRate,
|
||||
selected,
|
||||
state: selected ? 'selected' as const : 'candidate' as const,
|
||||
@@ -15955,7 +16025,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.add.text(
|
||||
x + 18,
|
||||
y + 9,
|
||||
`핵심 공명조 · ${selectedCore ? '지정' : '미지정'} · 후보 ${coreCandidates.length}조`,
|
||||
`핵심 공명조 · ${selectedCore ? `${selectedCore.coreChainRate}% 지정` : '미지정'} · 후보 ${coreCandidates.length}조`,
|
||||
this.textStyle(14, '#f2e3bf', true)
|
||||
)
|
||||
);
|
||||
@@ -15971,7 +16041,7 @@ export class CampScene extends Phaser.Scene {
|
||||
width - 36,
|
||||
depth + 1,
|
||||
x + width - 84,
|
||||
y + 60
|
||||
y + 72
|
||||
);
|
||||
if (pursuitPageView.rows.length === 0) {
|
||||
this.trackSortie(
|
||||
@@ -15979,7 +16049,14 @@ export class CampScene extends Phaser.Scene {
|
||||
).setDepth(depth + 1);
|
||||
}
|
||||
const ruleText = this.trackSortie(
|
||||
this.add.text(x + 18, y + 62, `Lv${coreSortieResonanceMinLevel}+ 한 조 · 핵심 추격 8/18/28% · 재클릭 해제`, this.textStyle(9, '#d4dce6', true))
|
||||
this.add.text(
|
||||
x + 18,
|
||||
y + 72,
|
||||
coreCandidates.some((candidate) => candidate.camaraderieRemembered)
|
||||
? `전우 기억한 조 직접 지정 · 핵심 추격 +${firstBattleCamaraderieCoreRateBonus}%p · 재클릭 해제`
|
||||
: `Lv${coreSortieResonanceMinLevel}+ 한 조 · 핵심 추격 8/18/28% · 재클릭 해제`,
|
||||
this.textStyle(9, '#d4dce6', true)
|
||||
)
|
||||
);
|
||||
ruleText.setDepth(depth + 1);
|
||||
const missingRoleLabels: Record<(typeof coreSortieSynergyRoles)[number], string> = {
|
||||
@@ -15994,7 +16071,7 @@ export class CampScene extends Phaser.Scene {
|
||||
: '삼재진 대기 · 세 형제의 역할을 나누십시오.';
|
||||
const bottomLine = this.sortiePlanFeedback ? `${formationLine} · ${this.sortiePlanFeedback}` : formationLine;
|
||||
this.trackSortie(
|
||||
this.add.text(x + 18, y + 88, this.compactText(bottomLine, 47), this.textStyle(11, synergy.firstPursuitTrinityConfigured ? '#a8ffd0' : '#ffdf7b', true))
|
||||
this.add.text(x + 18, y + 96, this.compactText(bottomLine, 47), this.textStyle(11, synergy.firstPursuitTrinityConfigured ? '#a8ffd0' : '#ffdf7b', true))
|
||||
).setDepth(depth + 1);
|
||||
this.sortiePursuitPanelView = { background: bg, summaryText, ruleText, ...pursuitPageView };
|
||||
}
|
||||
@@ -17252,13 +17329,27 @@ export class CampScene extends Phaser.Scene {
|
||||
const seenPairs = new Set<string>();
|
||||
return snapshot.activeBonds
|
||||
.filter((bond) => bond.level >= coreSortieResonanceMinLevel)
|
||||
.map((bond) => ({
|
||||
...bond,
|
||||
coreChainRate: coreSortieResonanceChainRateForLevel(bond.level),
|
||||
selected: bond.id === selectedBondId
|
||||
}))
|
||||
.map((bond) => {
|
||||
const baseCoreChainRate = coreSortieResonanceChainRateForLevel(bond.level);
|
||||
const camaraderieBonusRate = scenario
|
||||
? firstBattleCamaraderieBonusFor({
|
||||
campaign: this.campaign,
|
||||
battleId: scenario.id,
|
||||
coreBondId: bond.id
|
||||
})
|
||||
: 0;
|
||||
return {
|
||||
...bond,
|
||||
baseCoreChainRate,
|
||||
camaraderieBonusRate,
|
||||
coreChainRate: baseCoreChainRate + camaraderieBonusRate,
|
||||
camaraderieRemembered: camaraderieBonusRate > 0,
|
||||
selected: bond.id === selectedBondId
|
||||
};
|
||||
})
|
||||
.sort((left, right) =>
|
||||
Number(right.selected) - Number(left.selected) ||
|
||||
Number(right.camaraderieRemembered) - Number(left.camaraderieRemembered) ||
|
||||
right.coreChainRate - left.coreChainRate ||
|
||||
right.level - left.level ||
|
||||
left.title.localeCompare(right.title) ||
|
||||
@@ -17304,7 +17395,9 @@ export class CampScene extends Phaser.Scene {
|
||||
const pairLabel = `${candidate.unitNames[0]}↔${candidate.unitNames[1]}`;
|
||||
this.sortiePlanFeedback = clearing
|
||||
? `핵심 공명조 해제 · ${pairLabel}`
|
||||
: `핵심 공명조 지정 · ${pairLabel} · 추격 ${candidate.coreChainRate}%`;
|
||||
: candidate.camaraderieRemembered
|
||||
? `핵심 공명조 지정 · ${pairLabel} · 추격 ${candidate.baseCoreChainRate}% + 전우 기억 ${candidate.camaraderieBonusRate}%p = ${candidate.coreChainRate}%`
|
||||
: `핵심 공명조 지정 · ${pairLabel} · 추격 ${candidate.coreChainRate}%`;
|
||||
if (guidedRecalculated) {
|
||||
this.sortiePlanFeedback += ' · 보완안 재계산';
|
||||
}
|
||||
@@ -17326,7 +17419,8 @@ export class CampScene extends Phaser.Scene {
|
||||
const rule = this.nextSortieRule(scenario);
|
||||
const members = this.sortieRosterUnits().filter((unit) => this.sortieUnitAvailability(unit, scenario, rule).available);
|
||||
const availableIds = new Set(members.map((unit) => unit.id));
|
||||
return evaluateSortiePursuitPairs({
|
||||
const coreBondId = this.currentSortieResonanceBondId(scenario);
|
||||
const pursuit = evaluateSortiePursuitPairs({
|
||||
members: members.map((unit) => ({
|
||||
id: unit.id,
|
||||
name: unit.name,
|
||||
@@ -17337,8 +17431,28 @@ export class CampScene extends Phaser.Scene {
|
||||
selectedUnitIds: selectedUnitIds.filter((unitId) => availableIds.has(unitId)),
|
||||
selectableUnitIds: (selectableUnitIds ?? [...availableIds]).filter((unitId) => availableIds.has(unitId)),
|
||||
battleId: scenario?.id,
|
||||
coreBondId: this.currentSortieResonanceBondId(scenario)
|
||||
coreBondId
|
||||
});
|
||||
const camaraderieBonusRate = firstBattleCamaraderieBonusFor({
|
||||
campaign: this.campaign,
|
||||
battleId: scenario.id,
|
||||
coreBondId
|
||||
});
|
||||
if (camaraderieBonusRate <= 0 || !coreBondId) {
|
||||
return pursuit;
|
||||
}
|
||||
return {
|
||||
activePairs: pursuit.activePairs.map((pair) =>
|
||||
pair.core && pair.id === coreBondId
|
||||
? { ...pair, chainRate: pair.chainRate + camaraderieBonusRate }
|
||||
: pair
|
||||
),
|
||||
selectableOpportunities: pursuit.selectableOpportunities.map((opportunity) =>
|
||||
opportunity.core && opportunity.id === coreBondId
|
||||
? { ...opportunity, chainRate: opportunity.chainRate + camaraderieBonusRate }
|
||||
: opportunity
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
private sortiePursuitPairKey(pair: Pick<SortieSynergyActiveBond, 'unitIds'>) {
|
||||
@@ -17347,14 +17461,23 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private activeSortiePursuitPairs(snapshot: SortieSynergySnapshot) {
|
||||
const seenPairs = new Set<string>();
|
||||
const coreBondId = this.currentSortieResonanceBondId();
|
||||
const scenario = this.nextSortieScenario();
|
||||
const coreBondId = this.currentSortieResonanceBondId(scenario);
|
||||
const camaraderieBonusRate = scenario
|
||||
? firstBattleCamaraderieBonusFor({
|
||||
campaign: this.campaign,
|
||||
battleId: scenario.id,
|
||||
coreBondId
|
||||
})
|
||||
: 0;
|
||||
return snapshot.activeBonds
|
||||
.map((bond) => {
|
||||
const core = bond.id === coreBondId && bond.level >= coreSortieResonanceMinLevel;
|
||||
const baseCoreChainRate = coreSortieResonanceChainRateForLevel(bond.level);
|
||||
return {
|
||||
...bond,
|
||||
baseChainRate: bond.chainRate,
|
||||
chainRate: core ? coreSortieResonanceChainRateForLevel(bond.level) : bond.chainRate,
|
||||
chainRate: core ? baseCoreChainRate + camaraderieBonusRate : bond.chainRate,
|
||||
core
|
||||
};
|
||||
})
|
||||
@@ -22532,6 +22655,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
const dialogues = this.availableCampDialogues();
|
||||
const firstBattleFollowup = this.firstBattleCampFollowup();
|
||||
const camaraderieDialogue = this.firstBattleCamaraderieDialogue();
|
||||
const compactDialogueList = dialogues.length > 3;
|
||||
const dialogueRowGap = compactDialogueList ? 35 : 64;
|
||||
const dialogueRowHeight = compactDialogueList ? 30 : 48;
|
||||
@@ -22550,23 +22674,26 @@ export class CampScene extends Phaser.Scene {
|
||||
this.campDialogueRowButtons[dialogue.id] = row;
|
||||
row.on('pointerdown', () => {
|
||||
soundDirector.playSelect();
|
||||
this.clearCampNotice();
|
||||
this.selectedDialogueId = dialogue.id;
|
||||
this.render();
|
||||
});
|
||||
const isFirstBattleFollowup = firstBattleFollowup?.targetDialogueId === dialogue.id;
|
||||
const title = isFirstBattleFollowup
|
||||
const isCamaraderieMemory = camaraderieDialogue?.id === dialogue.id;
|
||||
const title = isFirstBattleFollowup || isCamaraderieMemory
|
||||
? 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) {
|
||||
if (isFirstBattleFollowup || isCamaraderieMemory) {
|
||||
const badgeLabel = completed ? '완료' : isCamaraderieMemory ? '전우 기억' : '회고';
|
||||
this.track(
|
||||
this.add.text(
|
||||
x + 320,
|
||||
rowY + (compactDialogueList ? 4 : 9),
|
||||
completed ? '완료' : '회고',
|
||||
badgeLabel,
|
||||
{
|
||||
...this.textStyle(compactDialogueList ? 8 : 9, completed ? '#a8ffd0' : '#fff2b8', true),
|
||||
backgroundColor: completed ? '#21402d' : '#8a3f25',
|
||||
backgroundColor: completed ? '#21402d' : isCamaraderieMemory ? '#36507a' : '#8a3f25',
|
||||
padding: { x: 4, y: 2 }
|
||||
}
|
||||
)
|
||||
@@ -23876,16 +24003,17 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
private showDialogueReward(dialogue: CampDialogue, choice: CampDialogueChoice, rewardExp: number) {
|
||||
const followup = this.firstBattleCampFollowup();
|
||||
const preparationLine = followup?.targetDialogueId === dialogue.id
|
||||
? `\n다음 준비 · ${followup.recommendationTitle}`
|
||||
: '';
|
||||
const camaraderieMemory = this.firstBattleCamaraderieMemory();
|
||||
const preparationLine = camaraderieMemory?.dialogue.id === dialogue.id
|
||||
? `\n다음 준비 · ${camaraderieMemory.unitNames.join('↔')}를 직접 핵심 공명조로 지정하면 추격 +${camaraderieMemory.bonusRate}%p · 자동 지정 없음`
|
||||
: followup?.targetDialogueId === dialogue.id
|
||||
? `\n다음 준비 · ${followup.recommendationTitle}`
|
||||
: '';
|
||||
this.showCampNotice(`획득 내역 · ${choice.label} · 공명 +${rewardExp}\n응답 · ${choice.response}${preparationLine}`);
|
||||
}
|
||||
|
||||
private showCampNotice(message: string) {
|
||||
this.tweens.killTweensOf(this.dialogueObjects);
|
||||
this.dialogueObjects.forEach((object) => object.active && object.destroy());
|
||||
this.dialogueObjects = [];
|
||||
this.clearCampNotice();
|
||||
const depth = this.sortieObjects.length > 0 ? 72 : 30;
|
||||
const longestLineLength = Math.max(...message.split('\n').map((line) => line.length));
|
||||
const noticeWidth = Math.min(760, Math.max(420, longestLineLength * 13));
|
||||
@@ -23917,6 +24045,12 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
|
||||
private clearCampNotice() {
|
||||
this.tweens.killTweensOf(this.dialogueObjects);
|
||||
this.dialogueObjects.forEach((object) => object.active && object.destroy());
|
||||
this.dialogueObjects = [];
|
||||
}
|
||||
|
||||
private campNoticeHoldDuration(message: string) {
|
||||
const readableCharacterCount = message.replace(/\s/g, '').length;
|
||||
const paragraphCount = Math.max(1, message.split('\n').filter((line) => line.trim().length > 0).length);
|
||||
@@ -24869,10 +25003,18 @@ export class CampScene extends Phaser.Scene {
|
||||
? this.completedCampDialogues().includes(cityStay.dialogue.id)
|
||||
: false;
|
||||
const firstBattleCampFollowup = this.firstBattleCampFollowup();
|
||||
const firstBattleCamaraderieMemory = this.firstBattleCamaraderieMemory();
|
||||
const firstBattleCamaraderieDialogue = this.firstBattleCamaraderieDialogue();
|
||||
const availableCampDialogues = this.availableCampDialogues();
|
||||
const selectedCampDialogue = availableCampDialogues.find(
|
||||
(dialogue) => dialogue.id === this.selectedDialogueId
|
||||
) ?? availableCampDialogues[0];
|
||||
const firstBattleCamaraderieCandidate = firstBattleCamaraderieMemory
|
||||
? sortieCoreResonanceCandidates.find((candidate) => candidate.id === firstBattleCamaraderieMemory.bondId)
|
||||
: undefined;
|
||||
const firstBattleCamaraderieCandidateRow = firstBattleCamaraderieMemory
|
||||
? this.sortiePursuitPanelView?.rows.find((row) => row.bondId === firstBattleCamaraderieMemory.bondId)
|
||||
: undefined;
|
||||
return {
|
||||
scene: this.scene.key,
|
||||
victoryRewardAcknowledgement: {
|
||||
@@ -24925,6 +25067,7 @@ export class CampScene extends Phaser.Scene {
|
||||
interactive: Boolean(button.bg.input?.enabled && button.text.input?.enabled),
|
||||
indicatorVisible: button.indicator.visible,
|
||||
newBadgeVisible: Boolean(button.newBadge?.visible),
|
||||
newBadgeText: button.newBadge?.text ?? null,
|
||||
fillColor: button.bg.fillColor,
|
||||
strokeColor: button.bg.strokeColor,
|
||||
lineWidth: button.bg.lineWidth
|
||||
@@ -24962,6 +25105,65 @@ export class CampScene extends Phaser.Scene {
|
||||
)
|
||||
}
|
||||
: null,
|
||||
firstBattleCamaraderieMemory: firstBattleCamaraderieMemory
|
||||
? {
|
||||
available: Boolean(firstBattleCamaraderieDialogue),
|
||||
sourceBattleId: firstBattleCamaraderieMemory.sourceBattleId,
|
||||
targetBattleId: firstBattleCamaraderieMemory.targetBattleId,
|
||||
bondId: firstBattleCamaraderieMemory.bondId,
|
||||
unitIds: [...firstBattleCamaraderieMemory.unitIds],
|
||||
unitNames: [...firstBattleCamaraderieMemory.unitNames],
|
||||
attempts: firstBattleCamaraderieMemory.attempts,
|
||||
successes: firstBattleCamaraderieMemory.successes,
|
||||
additionalDamage: firstBattleCamaraderieMemory.additionalDamage,
|
||||
rememberedOutcome: firstBattleCamaraderieMemory.rememberedOutcome,
|
||||
summary: firstBattleCamaraderieMemory.summary,
|
||||
completed: firstBattleCamaraderieMemory.completed,
|
||||
dialogue: {
|
||||
id: firstBattleCamaraderieMemory.dialogue.id,
|
||||
title: firstBattleCamaraderieMemory.dialogue.title,
|
||||
lines: [...firstBattleCamaraderieMemory.dialogue.lines],
|
||||
selected: this.selectedDialogueId === firstBattleCamaraderieMemory.dialogue.id,
|
||||
completed: this.completedCampDialogues().includes(firstBattleCamaraderieMemory.dialogue.id),
|
||||
choiceId: this.campaign?.campDialogueChoiceIds[firstBattleCamaraderieMemory.dialogue.id] ?? null,
|
||||
rowBounds: this.sortieObjectBoundsDebug(
|
||||
this.campDialogueRowButtons[firstBattleCamaraderieMemory.dialogue.id]
|
||||
),
|
||||
bodyBounds: this.selectedDialogueId === firstBattleCamaraderieMemory.dialogue.id
|
||||
? this.sortieTextBoundsDebug(this.campDialogueBodyText)
|
||||
: null,
|
||||
choices: firstBattleCamaraderieMemory.dialogue.choices.map((choice) => ({
|
||||
id: choice.id,
|
||||
label: choice.label,
|
||||
response: choice.response,
|
||||
rewardExp: firstBattleCamaraderieMemory.dialogue.rewardExp + choice.rewardExp,
|
||||
bounds: this.sortieInteractiveObjectBoundsDebug(
|
||||
this.campDialogueChoiceButtons[`${firstBattleCamaraderieMemory.dialogue.id}:${choice.id}`]
|
||||
)
|
||||
}))
|
||||
},
|
||||
perk: {
|
||||
requiresManualSelection: true,
|
||||
autoSelected: false,
|
||||
exactPairOnly: true,
|
||||
unlocked: firstBattleCamaraderieMemory.completed,
|
||||
expectedBonusRate: firstBattleCamaraderieMemory.bonusRate,
|
||||
candidateAvailable: Boolean(firstBattleCamaraderieCandidate),
|
||||
selected: Boolean(firstBattleCamaraderieCandidate?.selected),
|
||||
active: Boolean(
|
||||
firstBattleCamaraderieCandidate?.selected &&
|
||||
firstBattleCamaraderieCandidate.camaraderieRemembered
|
||||
),
|
||||
baseChainRate: firstBattleCamaraderieCandidate?.baseCoreChainRate ?? null,
|
||||
bonusChainRate: firstBattleCamaraderieCandidate?.camaraderieBonusRate ?? 0,
|
||||
effectiveChainRate: firstBattleCamaraderieCandidate?.coreChainRate ?? null,
|
||||
cardText: firstBattleCamaraderieCandidateRow?.text ?? null,
|
||||
cardBounds: this.sortieInteractiveObjectBoundsDebug(
|
||||
firstBattleCamaraderieCandidateRow?.background
|
||||
)
|
||||
}
|
||||
}
|
||||
: null,
|
||||
selectedDialogue: selectedCampDialogue
|
||||
? {
|
||||
id: selectedCampDialogue.id,
|
||||
@@ -24970,6 +25172,7 @@ export class CampScene extends Phaser.Scene {
|
||||
lines: [...selectedCampDialogue.lines],
|
||||
completed: this.completedCampDialogues().includes(selectedCampDialogue.id),
|
||||
firstVictoryFollowup: firstBattleCampFollowup?.targetDialogueId === selectedCampDialogue.id,
|
||||
camaraderieMemory: firstBattleCamaraderieMemory?.dialogue.id === selectedCampDialogue.id,
|
||||
rowBounds: this.sortieObjectBoundsDebug(this.campDialogueRowButtons[selectedCampDialogue.id]),
|
||||
bodyBounds: this.sortieTextBoundsDebug(this.campDialogueBodyText),
|
||||
choices: selectedCampDialogue.choices.map((choice) => ({
|
||||
@@ -25814,6 +26017,10 @@ export class CampScene extends Phaser.Scene {
|
||||
title: candidate.title,
|
||||
level: candidate.level,
|
||||
baseChainRate: candidate.chainRate,
|
||||
baseCoreChainRate: candidate.baseCoreChainRate,
|
||||
bonusChainRate: candidate.camaraderieBonusRate,
|
||||
effectiveCoreChainRate: candidate.coreChainRate,
|
||||
camaraderieRemembered: candidate.camaraderieRemembered,
|
||||
coreChainRate: candidate.coreChainRate,
|
||||
selected: candidate.selected
|
||||
})),
|
||||
@@ -25860,6 +26067,10 @@ export class CampScene extends Phaser.Scene {
|
||||
rows: this.sortiePursuitPanelView?.rows.map((row) => ({
|
||||
bondId: row.bondId,
|
||||
level: row.level,
|
||||
baseChainRate: row.baseChainRate,
|
||||
bonusChainRate: row.bonusChainRate,
|
||||
effectiveChainRate: row.effectiveChainRate,
|
||||
camaraderieRemembered: row.camaraderieRemembered,
|
||||
chainRate: row.chainRate,
|
||||
selected: row.selected,
|
||||
state: row.state,
|
||||
|
||||
Reference in New Issue
Block a user