Add selectable reserve drill focus
This commit is contained in:
@@ -53,12 +53,17 @@ import {
|
||||
import {
|
||||
applyCampVisitReward,
|
||||
applyCampBondExp,
|
||||
campaignReserveTrainingFocusDefinitions,
|
||||
defaultCampaignReserveTrainingFocusId,
|
||||
ensureCampaignRosterUnits,
|
||||
getCampaignState,
|
||||
getFirstBattleReport,
|
||||
markCampaignStep,
|
||||
saveCampaignState,
|
||||
setCampaignReserveTrainingFocus,
|
||||
type CampaignState,
|
||||
type CampaignReserveTrainingFocusDefinition,
|
||||
type CampaignReserveTrainingFocusId,
|
||||
type FirstBattleReport
|
||||
} from '../state/campaignState';
|
||||
import { palette } from '../ui/palette';
|
||||
@@ -175,6 +180,12 @@ type SortiePlanSummary = {
|
||||
recommendationLine: string;
|
||||
recruitedLine: string;
|
||||
reserveLine: string;
|
||||
reserveTrainingLine: string;
|
||||
reserveTrainingFocusId: CampaignReserveTrainingFocusId;
|
||||
reserveTrainingFocusLabel: string;
|
||||
reserveTrainingExpPreview: number;
|
||||
reserveTrainingEquipmentPreview: number;
|
||||
reserveTrainingBondPreview: number;
|
||||
objectiveLine: string;
|
||||
formationSlots: SortieFormationSlot[];
|
||||
warnings: string[];
|
||||
@@ -7085,7 +7096,8 @@ export class CampScene extends Phaser.Scene {
|
||||
this.renderSortieChecklist(x + 576, y + 104, 578, 168, depth + 2);
|
||||
this.renderSortieUnitSummary(x + 34, y + 292, 728, 286, depth + 2);
|
||||
this.renderSortieFormationPlan(x + 782, y + 292, 372, 286, depth + 2);
|
||||
this.renderSortieRewardHint(x + 34, y + 596, 808, 52, depth + 2);
|
||||
this.renderSortieRewardHint(x + 34, y + 596, 480, 52, depth + 2);
|
||||
this.renderSortieReserveTraining(x + 528, y + 596, 314, 52, depth + 2);
|
||||
|
||||
this.addSortieButton('군영으로', x + width - 298, y + height - 42, 124, () => {
|
||||
soundDirector.playSelect();
|
||||
@@ -7205,7 +7217,7 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
const summaryLines = [
|
||||
plan.deploymentLine,
|
||||
`${plan.recommendationLine} · 공명 ${plan.activeBondCount}개`,
|
||||
`${plan.recommendationLine} · 공명 ${plan.activeBondCount}개 · ${plan.reserveTrainingFocusLabel}`,
|
||||
plan.recruitedLine,
|
||||
plan.objectiveLine
|
||||
];
|
||||
@@ -7416,6 +7428,7 @@ export class CampScene extends Phaser.Scene {
|
||||
const selectedIds = new Set(selectedUnits.map((unit) => unit.id));
|
||||
const scenario = this.nextSortieScenario();
|
||||
const rule = this.nextSortieRule(scenario);
|
||||
const reserveFocus = this.reserveTrainingFocusDefinition();
|
||||
const availableIds = new Set(allAllies.map((unit) => unit.id));
|
||||
const recommended = rule.recommended.filter((entry) => availableIds.has(entry.unitId));
|
||||
const missingRecommended = recommended.filter((entry) => !selectedIds.has(entry.unitId));
|
||||
@@ -7484,6 +7497,15 @@ export class CampScene extends Phaser.Scene {
|
||||
? `합류 무장 ${selectedRecruitedCount}/${recruitedUnits.length} · 대기 ${reserveUnits.length}`
|
||||
: '합류 무장 없음',
|
||||
reserveLine: reserveUnits.length > 0 ? `대기 무장: ${reserveNames}` : '전원 출전 중입니다.',
|
||||
reserveTrainingLine:
|
||||
reserveUnits.length > 0
|
||||
? `대기 훈련 ${reserveFocus.label}: 경험 +${reserveFocus.expGained}, 장비 +${reserveFocus.equipmentExpGained}${reserveFocus.bondExpGained ? `, 공명 +${reserveFocus.bondExpGained}` : ''}`
|
||||
: '대기 훈련 대상 없음',
|
||||
reserveTrainingFocusId: reserveFocus.id,
|
||||
reserveTrainingFocusLabel: reserveFocus.label,
|
||||
reserveTrainingExpPreview: reserveFocus.expGained,
|
||||
reserveTrainingEquipmentPreview: reserveFocus.equipmentExpGained,
|
||||
reserveTrainingBondPreview: reserveFocus.bondExpGained,
|
||||
objectiveLine: scenario ? `${scenario.title} · ${scenario.victoryConditionLabel}` : '다음 전투 정보 없음',
|
||||
formationSlots,
|
||||
warnings
|
||||
@@ -7557,6 +7579,43 @@ export class CampScene extends Phaser.Scene {
|
||||
).setDepth(depth + 1);
|
||||
}
|
||||
|
||||
private renderSortieReserveTraining(x: number, y: number, width: number, height: number, depth: number) {
|
||||
const bg = this.trackSortie(this.add.rectangle(x, y, width, height, 0x151f2a, 0.9));
|
||||
bg.setOrigin(0);
|
||||
bg.setDepth(depth);
|
||||
bg.setStrokeStyle(1, palette.blue, 0.4);
|
||||
|
||||
const focus = this.reserveTrainingFocusDefinition();
|
||||
const reserveCount = this.sortiePlanSummary().reserveCount;
|
||||
this.trackSortie(this.add.text(x + 12, y + 7, '대기 훈련', this.textStyle(12, '#f2e3bf', true))).setDepth(depth + 1);
|
||||
this.trackSortie(
|
||||
this.add.text(
|
||||
x + width - 12,
|
||||
y + 7,
|
||||
reserveCount > 0 ? `${reserveCount}명 · ${focus.label}` : '대기 없음',
|
||||
this.textStyle(11, reserveCount > 0 ? '#d8b15f' : '#9fb0bf', true)
|
||||
)
|
||||
).setOrigin(1, 0).setDepth(depth + 1);
|
||||
|
||||
const buttonWidth = Math.floor((width - 40) / campaignReserveTrainingFocusDefinitions.length);
|
||||
campaignReserveTrainingFocusDefinitions.forEach((option, index) => {
|
||||
const buttonX = x + 12 + index * (buttonWidth + 8);
|
||||
const selected = option.id === focus.id;
|
||||
const button = this.trackSortie(this.add.rectangle(buttonX, y + 30, buttonWidth, 16, selected ? 0x263a2d : 0x0e151d, selected ? 0.98 : 0.86));
|
||||
button.setOrigin(0);
|
||||
button.setDepth(depth + 1);
|
||||
button.setStrokeStyle(1, selected ? palette.green : 0x53606c, selected ? 0.78 : 0.42);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
button.on('pointerdown', () => this.setReserveTrainingFocus(option.id));
|
||||
|
||||
const label = this.trackSortie(this.add.text(buttonX + buttonWidth / 2, y + 33, option.label, this.textStyle(10, selected ? '#a8ffd0' : '#c8d2dd', true)));
|
||||
label.setOrigin(0.5, 0);
|
||||
label.setDepth(depth + 2);
|
||||
label.setInteractive({ useHandCursor: true });
|
||||
label.on('pointerdown', () => this.setReserveTrainingFocus(option.id));
|
||||
});
|
||||
}
|
||||
|
||||
private nextSortieBriefing() {
|
||||
const flow = this.currentSortieFlow();
|
||||
return {
|
||||
@@ -7570,6 +7629,17 @@ export class CampScene extends Phaser.Scene {
|
||||
return getSortieFlow(this.campaign?.latestBattleId, this.campaign?.step);
|
||||
}
|
||||
|
||||
private reserveTrainingFocusDefinition(): CampaignReserveTrainingFocusDefinition {
|
||||
const focusId = this.campaign?.reserveTrainingFocus ?? defaultCampaignReserveTrainingFocusId;
|
||||
return campaignReserveTrainingFocusDefinitions.find((focus) => focus.id === focusId) ?? campaignReserveTrainingFocusDefinitions[0];
|
||||
}
|
||||
|
||||
private setReserveTrainingFocus(focusId: CampaignReserveTrainingFocusId) {
|
||||
this.campaign = setCampaignReserveTrainingFocus(focusId);
|
||||
soundDirector.playSelect();
|
||||
this.showSortiePrep();
|
||||
}
|
||||
|
||||
private sortieChecklist(): SortieChecklistItem[] {
|
||||
const units = this.currentUnits().filter((unit) => unit.faction === 'ally');
|
||||
const liuBei = units.find((unit) => unit.id === 'liu-bei');
|
||||
@@ -7739,7 +7809,9 @@ export class CampScene extends Phaser.Scene {
|
||||
const textX = compact ? x + 102 : x + 116;
|
||||
this.track(this.add.text(textX, y + (compact ? 7 : 17), `${unit.name} Lv ${unit.level}`, this.textStyle(compact ? 14 : 20, '#f2e3bf', true)));
|
||||
this.track(this.add.text(textX, y + (compact ? 27 : 47), `${unit.className} · HP ${unit.hp}/${unit.maxHp}`, this.textStyle(compact ? 10 : 13, '#d4dce6')));
|
||||
const expLine = training ? `경험 ${unit.exp}/100 · 대기 +${training.expGained}` : `경험 ${unit.exp}/100`;
|
||||
const expLine = training
|
||||
? `경험 ${unit.exp}/100 · ${training.focusLabel ?? '대기'} +${training.expGained}`
|
||||
: `경험 ${unit.exp}/100`;
|
||||
this.track(this.add.text(textX, y + (compact ? 40 : 72), expLine, this.textStyle(compact ? 10 : 13, training ? '#a8ffd0' : '#d8b15f', true)));
|
||||
this.drawBar(textX, y + cardHeight - (compact ? 10 : 17), compact ? 142 : 176, compact ? 5 : 8, unit.exp / 100, training ? palette.green : palette.gold);
|
||||
const badgeColor = rosterStatus.tone === 'selected' ? '#a8ffd0' : rosterStatus.tone === 'recommended' ? '#ffdf7b' : '#9fb0bf';
|
||||
@@ -7760,7 +7832,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.add.text(
|
||||
x + width - 12,
|
||||
y + 6,
|
||||
recentTrainingCount > 0 ? `최근 훈련 ${recentTrainingCount}` : '훈련 대기',
|
||||
recentTrainingCount > 0 ? `최근 훈련 ${recentTrainingCount} · ${this.latestReserveTrainingFocusLabel()}` : '훈련 대기',
|
||||
this.textStyle(11, recentTrainingCount > 0 ? '#a8ffd0' : '#9fb0bf', true)
|
||||
)
|
||||
);
|
||||
@@ -8009,6 +8081,10 @@ export class CampScene extends Phaser.Scene {
|
||||
return this.campaign?.battleHistory[latestBattleId]?.reserveTraining ?? [];
|
||||
}
|
||||
|
||||
private latestReserveTrainingFocusLabel() {
|
||||
return this.latestReserveTrainingAwards()[0]?.focusLabel ?? '대기';
|
||||
}
|
||||
|
||||
private latestReserveTraining(unitId: string) {
|
||||
return this.latestReserveTrainingAwards().find((entry) => entry.unitId === unitId);
|
||||
}
|
||||
@@ -8026,6 +8102,7 @@ export class CampScene extends Phaser.Scene {
|
||||
reserveCount: Math.max(0, allies.length - selectedCount),
|
||||
recruitedCount,
|
||||
averageLevel,
|
||||
reserveTrainingFocus: this.reserveTrainingFocusDefinition(),
|
||||
reserveTrainingCount: reserveTrainingAwards.length,
|
||||
reserveTrainingExp: reserveTrainingAwards.reduce((sum, entry) => sum + entry.expGained, 0)
|
||||
};
|
||||
@@ -8594,7 +8671,9 @@ export class CampScene extends Phaser.Scene {
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, training ? palette.green : palette.blue, training ? 0.5 : 0.34);
|
||||
const planText = recommendation ? `추천: ${recommendation.reason}` : `${roleLabel} 역할로 편성 가능`;
|
||||
const trainingText = training ? `최근 대기 훈련 경험 +${training.expGained}, 장비 +${training.equipmentExpGained}` : '대기 중에도 전투 후 소량의 훈련 성장이 반영됩니다.';
|
||||
const trainingText = training
|
||||
? `최근 ${training.focusLabel ?? '대기 훈련'} 경험 +${training.expGained}, 장비 +${training.equipmentExpGained}${training.bondExpGained ? `, 공명 +${training.bondExpGained}` : ''}`
|
||||
: '대기 중에도 전투 후 선택한 훈련 방침의 성장이 반영됩니다.';
|
||||
this.track(this.add.text(x + 12, y + 5, `${status.label} · ${roleLabel}`, this.textStyle(11, status.tone === 'reserve' ? '#9fb0bf' : '#f2e3bf', true)));
|
||||
this.track(this.add.text(x + 112, y + 5, this.compactText(planText, 48), this.textStyle(11, '#d4dce6')));
|
||||
const trainingLabel = this.track(this.add.text(x + width - 12, y + 5, this.compactText(trainingText, 28), this.textStyle(11, training ? '#a8ffd0' : '#9fb0bf', true)));
|
||||
@@ -8888,6 +8967,8 @@ export class CampScene extends Phaser.Scene {
|
||||
selectedDialogueId: this.selectedDialogueId,
|
||||
selectedVisitId: this.selectedVisitId,
|
||||
selectedSortieUnitIds: [...this.selectedSortieUnitIds],
|
||||
reserveTrainingFocus: this.reserveTrainingFocusDefinition(),
|
||||
reserveTrainingFocusOptions: campaignReserveTrainingFocusDefinitions.map((focus) => ({ ...focus })),
|
||||
sortieRoster: this.sortieAllies().map((unit) => {
|
||||
const summary = this.sortieUnitTacticalSummary(unit);
|
||||
return {
|
||||
@@ -8923,6 +9004,7 @@ export class CampScene extends Phaser.Scene {
|
||||
gold: this.campaign.gold,
|
||||
inventory: this.campaign.inventory,
|
||||
selectedSortieUnitIds: this.campaign.selectedSortieUnitIds,
|
||||
reserveTrainingFocus: this.campaign.reserveTrainingFocus,
|
||||
roster: this.campaign.roster.map((unit) => ({
|
||||
id: unit.id,
|
||||
name: unit.name,
|
||||
|
||||
Reference in New Issue
Block a user