Improve sortie formation readability
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import Phaser from 'phaser';
|
import Phaser from 'phaser';
|
||||||
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
|
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
|
||||||
import { soundDirector } from '../audio/SoundDirector';
|
import { soundDirector } from '../audio/SoundDirector';
|
||||||
|
import { battleUiIconFrames, battleUiIconTextureKey, loadBattleUiIcons, type BattleUiIconKey } from '../data/battleUiIcons';
|
||||||
import { equipmentExpToNext, equipmentSlotLabels, equipmentSlots, getItem, type EquipmentSlot } from '../data/battleItems';
|
import { equipmentExpToNext, equipmentSlotLabels, equipmentSlots, getItem, type EquipmentSlot } from '../data/battleItems';
|
||||||
import { getUnitClass, terrainRules, type TerrainType } from '../data/battleRules';
|
import { getUnitClass, terrainRules, type TerrainType } from '../data/battleRules';
|
||||||
import { defaultBattleScenario, getBattleScenario, type BattleScenarioDefinition, type BattleScenarioId } from '../data/battles';
|
import { defaultBattleScenario, getBattleScenario, type BattleScenarioDefinition, type BattleScenarioId } from '../data/battles';
|
||||||
@@ -10021,7 +10022,7 @@ export class CampScene extends Phaser.Scene {
|
|||||||
this.selectedDialogueId = this.availableCampDialogues()[0]?.id ?? campDialogues[0].id;
|
this.selectedDialogueId = this.availableCampDialogues()[0]?.id ?? campDialogues[0].id;
|
||||||
this.selectedVisitId = this.availableCampVisits()[0]?.id ?? campVisits[0].id;
|
this.selectedVisitId = this.availableCampVisits()[0]?.id ?? campVisits[0].id;
|
||||||
soundDirector.playMusic('militia-theme');
|
soundDirector.playMusic('militia-theme');
|
||||||
this.ensureCampAssets(() => this.drawCampScene());
|
loadBattleUiIcons(this, () => this.ensureCampAssets(() => this.drawCampScene()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawCampScene() {
|
private drawCampScene() {
|
||||||
@@ -11229,6 +11230,7 @@ export class CampScene extends Phaser.Scene {
|
|||||||
this.add.text(x + 34, y + 70, prepSubtitle, this.textStyle(15, '#d4dce6'))
|
this.add.text(x + 34, y + 70, prepSubtitle, this.textStyle(15, '#d4dce6'))
|
||||||
);
|
);
|
||||||
subtitle.setDepth(depth + 2);
|
subtitle.setDepth(depth + 2);
|
||||||
|
this.renderSortieHeaderSummary(x + 690, y + 26, 464, 58, depth + 2);
|
||||||
|
|
||||||
this.renderSortieBriefing(x + 34, y + 104, 520, 168, depth + 2);
|
this.renderSortieBriefing(x + 34, y + 104, 520, 168, depth + 2);
|
||||||
this.renderSortieChecklist(x + 576, y + 104, 578, 168, depth + 2);
|
this.renderSortieChecklist(x + 576, y + 104, 578, 168, depth + 2);
|
||||||
@@ -11247,6 +11249,37 @@ export class CampScene extends Phaser.Scene {
|
|||||||
}, depth + 3);
|
}, depth + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderSortieHeaderSummary(x: number, y: number, width: number, height: number, depth: number) {
|
||||||
|
const selectedUnits = this.selectedSortieUnits();
|
||||||
|
const selectedIds = new Set(selectedUnits.map((unit) => unit.id));
|
||||||
|
const reserveUnits = this.sortieAllies().filter((unit) => !selectedIds.has(unit.id));
|
||||||
|
const selectedNames = selectedUnits.map((unit) => unit.name).join(', ');
|
||||||
|
const reserveNames = reserveUnits.map((unit) => unit.name).join(', ');
|
||||||
|
const bg = this.trackSortie(this.add.rectangle(x, y, width, height, 0x0d141c, 0.78));
|
||||||
|
bg.setOrigin(0);
|
||||||
|
bg.setDepth(depth);
|
||||||
|
bg.setStrokeStyle(1, palette.gold, 0.36);
|
||||||
|
|
||||||
|
this.renderSortieBattleUiIcon('leadership', x + 22, y + 20, 24, depth + 1, 0.95);
|
||||||
|
this.trackSortie(
|
||||||
|
this.add.text(
|
||||||
|
x + 44,
|
||||||
|
y + 10,
|
||||||
|
this.compactText(`출전 ${selectedUnits.length}명: ${selectedNames || '없음'}`, 35),
|
||||||
|
this.textStyle(13, '#f2e3bf', true)
|
||||||
|
)
|
||||||
|
).setDepth(depth + 1);
|
||||||
|
this.renderSortieBattleUiIcon('focus', x + 22, y + 44, 22, depth + 1, 0.74);
|
||||||
|
this.trackSortie(
|
||||||
|
this.add.text(
|
||||||
|
x + 44,
|
||||||
|
y + 35,
|
||||||
|
this.compactText(`대기 ${reserveUnits.length}명: ${reserveNames || '없음'}`, 35),
|
||||||
|
this.textStyle(12, reserveUnits.length > 0 ? '#c8d2dd' : '#9fb0bf', true)
|
||||||
|
)
|
||||||
|
).setDepth(depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
private renderSortieBriefing(x: number, y: number, width: number, height: number, depth: number) {
|
private renderSortieBriefing(x: number, y: number, width: number, height: number, depth: number) {
|
||||||
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);
|
||||||
@@ -11295,12 +11328,14 @@ export class CampScene extends Phaser.Scene {
|
|||||||
bg.setDepth(depth);
|
bg.setDepth(depth);
|
||||||
bg.setStrokeStyle(1, palette.blue, 0.48);
|
bg.setStrokeStyle(1, palette.blue, 0.48);
|
||||||
const allies = this.sortieAllies();
|
const allies = this.sortieAllies();
|
||||||
|
const displayAllies = this.sortieRosterDisplayUnits(allies);
|
||||||
const selectedCount = this.selectedSortieUnitIds.length;
|
const selectedCount = this.selectedSortieUnitIds.length;
|
||||||
const maxUnits = this.sortieMaxUnits();
|
const maxUnits = this.sortieMaxUnits();
|
||||||
|
const reserveCount = Math.max(0, allies.length - selectedCount);
|
||||||
const rosterView = this.sortieRosterViewState(allies.length, height);
|
const rosterView = this.sortieRosterViewState(allies.length, height);
|
||||||
const { denseRoster, rowGap, rowHeight, listTopOffset, listFooterHeight, maxVisibleRows, maxScroll } = rosterView;
|
const { denseRoster, rowGap, rowHeight, listTopOffset, listFooterHeight, maxVisibleRows, maxScroll } = rosterView;
|
||||||
this.sortieRosterScroll = Phaser.Math.Clamp(this.sortieRosterScroll, 0, maxScroll);
|
this.sortieRosterScroll = Phaser.Math.Clamp(this.sortieRosterScroll, 0, maxScroll);
|
||||||
const visibleAllies = allies.slice(this.sortieRosterScroll, this.sortieRosterScroll + maxVisibleRows);
|
const visibleAllies = displayAllies.slice(this.sortieRosterScroll, this.sortieRosterScroll + maxVisibleRows);
|
||||||
const handleRosterWheel = (
|
const handleRosterWheel = (
|
||||||
_pointer: Phaser.Input.Pointer,
|
_pointer: Phaser.Input.Pointer,
|
||||||
_deltaX: number,
|
_deltaX: number,
|
||||||
@@ -11320,9 +11355,9 @@ export class CampScene extends Phaser.Scene {
|
|||||||
bg.on('wheel', handleRosterWheel);
|
bg.on('wheel', handleRosterWheel);
|
||||||
}
|
}
|
||||||
this.trackSortie(
|
this.trackSortie(
|
||||||
this.add.text(x + 18, y + 14, `출전 무장 선택 ${selectedCount}/${maxUnits}`, this.textStyle(18, '#f2e3bf', true))
|
this.add.text(x + 18, y + 14, `출전 ${selectedCount}/${maxUnits} · 대기 ${reserveCount}`, this.textStyle(18, '#f2e3bf', true))
|
||||||
).setDepth(depth + 1);
|
).setDepth(depth + 1);
|
||||||
this.trackSortie(this.add.text(x + width - 18, y + 17, '클릭: 분석/출전 전환', this.textStyle(11, '#9fb0bf'))).setOrigin(1, 0).setDepth(depth + 1);
|
this.trackSortie(this.add.text(x + width - 18, y + 17, '선택 무장 우선 표시 · 클릭 전환', this.textStyle(11, '#9fb0bf'))).setOrigin(1, 0).setDepth(depth + 1);
|
||||||
|
|
||||||
visibleAllies.forEach((unit, index) => {
|
visibleAllies.forEach((unit, index) => {
|
||||||
const rowY = y + listTopOffset + index * rowGap;
|
const rowY = y + listTopOffset + index * rowGap;
|
||||||
@@ -11331,10 +11366,11 @@ export class CampScene extends Phaser.Scene {
|
|||||||
const recruited = !foundingSortieUnitIds.has(unit.id);
|
const recruited = !foundingSortieUnitIds.has(unit.id);
|
||||||
const recommendation = this.sortieRecommendation(unit.id);
|
const recommendation = this.sortieRecommendation(unit.id);
|
||||||
const summary = this.sortieUnitTacticalSummary(unit);
|
const summary = this.sortieUnitTacticalSummary(unit);
|
||||||
const row = this.trackSortie(this.add.rectangle(x + 18, rowY - 5, width - 36, rowHeight, selected ? 0x172a22 : 0x151b24, selected ? 0.96 : 0.82));
|
const recommendedReserve = Boolean(recommendation && !selected);
|
||||||
|
const row = this.trackSortie(this.add.rectangle(x + 18, rowY - 5, width - 36, rowHeight, selected ? 0x172a22 : recommendedReserve ? 0x241f17 : 0x151b24, selected ? 0.96 : 0.84));
|
||||||
row.setOrigin(0);
|
row.setOrigin(0);
|
||||||
row.setDepth(depth + 1);
|
row.setDepth(depth + 1);
|
||||||
row.setStrokeStyle(1, selected ? palette.green : 0x53606c, selected ? 0.62 : 0.38);
|
row.setStrokeStyle(1, selected ? palette.green : recommendedReserve ? palette.gold : 0x53606c, selected ? 0.7 : recommendedReserve ? 0.54 : 0.36);
|
||||||
row.setInteractive({ useHandCursor: true });
|
row.setInteractive({ useHandCursor: true });
|
||||||
row.on('wheel', handleRosterWheel);
|
row.on('wheel', handleRosterWheel);
|
||||||
row.on('pointerover', () => {
|
row.on('pointerover', () => {
|
||||||
@@ -11342,22 +11378,25 @@ export class CampScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
row.on('pointerdown', () => this.toggleSortieUnit(unit.id));
|
row.on('pointerdown', () => this.toggleSortieUnit(unit.id));
|
||||||
|
|
||||||
const marker = selected ? '출전' : '대기';
|
const markerLabel = required ? '필수 출전' : selected ? '출전' : recommendation ? '추천 대기' : recruited ? '합류 대기' : '대기';
|
||||||
const markerLabel = required ? '필수' : recommendation ? '추천' : recruited ? '합류' : marker;
|
this.trackSortie(this.add.text(x + 30, rowY, markerLabel, this.textStyle(denseRoster ? 9 : 11, required || recommendation ? '#ffdf7b' : selected ? '#a8ffd0' : '#9fb0bf', true))).setDepth(depth + 2);
|
||||||
this.trackSortie(this.add.text(x + 30, rowY, markerLabel, this.textStyle(denseRoster ? 10 : 12, required || recommendation ? '#ffdf7b' : selected ? '#a8ffd0' : '#9fb0bf', true))).setDepth(depth + 2);
|
if (!denseRoster) {
|
||||||
this.trackSortie(this.add.text(x + (denseRoster ? 78 : 84), rowY, `${unit.name} Lv ${unit.level}`, this.textStyle(denseRoster ? 12 : 14, selected ? '#f2e3bf' : '#87919c', true))).setDepth(depth + 2);
|
this.renderSortieBattleUiIcon(this.unitClassIconKey(unit), x + 90, rowY + 8, 20, depth + 2, selected ? 1 : 0.58);
|
||||||
this.trackSortie(this.add.text(x + (denseRoster ? 194 : 214), rowY, this.compactText(`${unit.className} · ${getUnitClass(unit.classKey).role}`, denseRoster ? 17 : 19), this.textStyle(denseRoster ? 10 : 12, selected ? '#d4dce6' : '#77818c', true))).setDepth(depth + 2);
|
}
|
||||||
|
this.trackSortie(this.add.text(x + (denseRoster ? 84 : 112), rowY, `${unit.name} Lv ${unit.level}`, this.textStyle(denseRoster ? 12 : 14, selected ? '#f2e3bf' : recommendedReserve ? '#d8b15f' : '#87919c', true))).setDepth(depth + 2);
|
||||||
|
this.trackSortie(this.add.text(x + (denseRoster ? 200 : 234), rowY, this.compactText(`${unit.className} · ${getUnitClass(unit.classKey).role}`, denseRoster ? 17 : 17), this.textStyle(denseRoster ? 10 : 12, selected ? '#d4dce6' : '#77818c', true))).setDepth(depth + 2);
|
||||||
this.trackSortie(this.add.text(x + 424, rowY, denseRoster ? this.compactText(summary.statLine, 23) : `병력 ${unit.hp}/${unit.maxHp}`, this.textStyle(denseRoster ? 10 : 12, selected ? '#d4dce6' : '#77818c', true))).setDepth(depth + 2);
|
this.trackSortie(this.add.text(x + 424, rowY, denseRoster ? this.compactText(summary.statLine, 23) : `병력 ${unit.hp}/${unit.maxHp}`, this.textStyle(denseRoster ? 10 : 12, selected ? '#d4dce6' : '#77818c', true))).setDepth(depth + 2);
|
||||||
if (!denseRoster) {
|
if (!denseRoster) {
|
||||||
this.drawSortieBar(x + 514, rowY + 6, 74, 7, unit.hp / unit.maxHp, selected ? palette.green : 0x53606c, depth + 2);
|
this.drawSortieBar(x + 514, rowY + 6, 74, 7, unit.hp / unit.maxHp, selected ? palette.green : 0x53606c, depth + 2);
|
||||||
}
|
}
|
||||||
this.trackSortie(this.add.text(x + 610, rowY, this.compactText(denseRoster ? summary.bondLine : summary.terrainLine, denseRoster ? 15 : 13), this.textStyle(11, selected ? (denseRoster ? '#a8ffd0' : '#d8b15f') : '#77818c', true))).setDepth(depth + 2);
|
this.trackSortie(this.add.text(x + 610, rowY, this.compactText(denseRoster ? summary.bondLine : summary.terrainLine, denseRoster ? 15 : 13), this.textStyle(11, selected ? (denseRoster ? '#a8ffd0' : '#d8b15f') : recommendedReserve ? '#d8b15f' : '#77818c', true))).setDepth(depth + 2);
|
||||||
|
|
||||||
if (!denseRoster) {
|
if (!denseRoster) {
|
||||||
this.renderSortieEquipmentIcons(unit, x + 84, rowY + 19, selected, depth + 2);
|
this.renderSortieEquipmentIcons(unit, x + 112, rowY + 19, selected, depth + 2);
|
||||||
this.trackSortie(this.add.text(x + 162, rowY + 18, this.compactText(summary.equipmentLine, 34), this.textStyle(11, selected ? '#c8d2dd' : '#68727e'))).setDepth(depth + 2);
|
this.trackSortie(this.add.text(x + 190, rowY + 18, this.compactText(summary.equipmentLine, 29), this.textStyle(11, selected ? '#c8d2dd' : '#68727e'))).setDepth(depth + 2);
|
||||||
this.trackSortie(this.add.text(x + 424, rowY + 18, this.compactText(summary.statLine, 23), this.textStyle(11, selected ? '#c8d2dd' : '#68727e'))).setDepth(depth + 2);
|
this.trackSortie(this.add.text(x + 424, rowY + 18, this.compactText(summary.statLine, 23), this.textStyle(11, selected ? '#c8d2dd' : '#68727e'))).setDepth(depth + 2);
|
||||||
this.trackSortie(this.add.text(x + 610, rowY + 18, this.compactText(summary.bondLine, 15), this.textStyle(11, selected ? '#a8ffd0' : '#68727e', selected))).setDepth(depth + 2);
|
const secondLine = recommendation && !selected ? `추천: ${recommendation.reason}` : summary.bondLine;
|
||||||
|
this.trackSortie(this.add.text(x + 610, rowY + 18, this.compactText(secondLine, 17), this.textStyle(11, selected ? '#a8ffd0' : recommendedReserve ? '#ffdf7b' : '#68727e', selected || recommendedReserve))).setDepth(depth + 2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -11388,6 +11427,29 @@ export class CampScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sortieRosterDisplayUnits(allies: UnitData[]) {
|
||||||
|
const selected = new Set(this.selectedSortieUnitIds);
|
||||||
|
const order = new Map(allies.map((unit, index) => [unit.id, index]));
|
||||||
|
return [...allies].sort((a, b) => {
|
||||||
|
const aSelected = selected.has(a.id);
|
||||||
|
const bSelected = selected.has(b.id);
|
||||||
|
if (aSelected !== bSelected) {
|
||||||
|
return Number(bSelected) - Number(aSelected);
|
||||||
|
}
|
||||||
|
const aRecommended = Boolean(this.sortieRecommendation(a.id));
|
||||||
|
const bRecommended = Boolean(this.sortieRecommendation(b.id));
|
||||||
|
if (aRecommended !== bRecommended) {
|
||||||
|
return Number(bRecommended) - Number(aRecommended);
|
||||||
|
}
|
||||||
|
const aRequired = this.isRequiredSortieUnit(a.id);
|
||||||
|
const bRequired = this.isRequiredSortieUnit(b.id);
|
||||||
|
if (aRequired !== bRequired) {
|
||||||
|
return Number(bRequired) - Number(aRequired);
|
||||||
|
}
|
||||||
|
return (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private updateSortieRosterScroll(nextScroll: number, maxScroll: number) {
|
private updateSortieRosterScroll(nextScroll: number, maxScroll: number) {
|
||||||
const clamped = Phaser.Math.Clamp(Math.round(nextScroll), 0, maxScroll);
|
const clamped = Phaser.Math.Clamp(Math.round(nextScroll), 0, maxScroll);
|
||||||
if (clamped === this.sortieRosterScroll) {
|
if (clamped === this.sortieRosterScroll) {
|
||||||
@@ -11431,19 +11493,38 @@ export class CampScene extends Phaser.Scene {
|
|||||||
this.add.text(x + width - 18, y + 17, `${plan.selectedCount}/${plan.maxCount}명 · 지형 ${plan.terrainGrade}`, this.textStyle(12, '#d8b15f', true))
|
this.add.text(x + width - 18, y + 17, `${plan.selectedCount}/${plan.maxCount}명 · 지형 ${plan.terrainGrade}`, this.textStyle(12, '#d8b15f', true))
|
||||||
).setOrigin(1, 0).setDepth(depth + 1);
|
).setOrigin(1, 0).setDepth(depth + 1);
|
||||||
|
|
||||||
this.renderSortieDeploymentMap(x + 18, y + 46, width - 36, 92, depth + 1);
|
this.renderSortieDeploymentMap(x + 18, y + 46, width - 36, 84, depth + 1);
|
||||||
|
this.renderSortieRoleSlots(x + 18, y + 136, width - 36, 48, plan, depth + 1);
|
||||||
|
this.trackSortie(
|
||||||
|
this.add.text(
|
||||||
|
x + 18,
|
||||||
|
y + 190,
|
||||||
|
this.compactText(`${plan.recommendationLine} · 공명 ${plan.activeBondCount}개 · ${plan.objectiveLine}`, 44),
|
||||||
|
this.textStyle(11, '#d4dce6', true)
|
||||||
|
)
|
||||||
|
).setDepth(depth + 1);
|
||||||
|
|
||||||
const summaryLines = [
|
this.renderSortieFocusPanel(x + 18, y + 204, width - 36, 74, depth + 1);
|
||||||
plan.deploymentLine,
|
}
|
||||||
`${plan.recommendationLine} · 공명 ${plan.activeBondCount}개 · ${plan.reserveTrainingFocusLabel}`,
|
|
||||||
plan.recruitedLine,
|
private renderSortieRoleSlots(x: number, y: number, width: number, height: number, plan: SortiePlanSummary, depth: number) {
|
||||||
plan.objectiveLine
|
const gap = 8;
|
||||||
];
|
const slotWidth = Math.floor((width - gap) / 2);
|
||||||
summaryLines.forEach((line, index) => {
|
const slotHeight = Math.floor((height - 6) / 2);
|
||||||
this.trackSortie(this.add.text(x + 18, y + 142 + index * 15, this.compactText(line, 32), this.textStyle(12, index === 0 ? '#f2e3bf' : '#d4dce6', index === 0))).setDepth(depth + 1);
|
plan.formationSlots.forEach((slot, index) => {
|
||||||
|
const column = index % 2;
|
||||||
|
const row = Math.floor(index / 2);
|
||||||
|
const slotX = x + column * (slotWidth + gap);
|
||||||
|
const slotY = y + row * (slotHeight + 6);
|
||||||
|
const filled = slot.unitNames.length > 0;
|
||||||
|
const bg = this.trackSortie(this.add.rectangle(slotX, slotY, slotWidth, slotHeight, filled ? 0x172a22 : 0x111922, filled ? 0.94 : 0.78));
|
||||||
|
bg.setOrigin(0);
|
||||||
|
bg.setDepth(depth);
|
||||||
|
bg.setStrokeStyle(1, filled ? palette.green : 0x53606c, filled ? 0.58 : 0.34);
|
||||||
|
this.renderSortieBattleUiIcon(this.formationRoleIconKey(slot.role), slotX + 13, slotY + slotHeight / 2, 18, depth + 1, filled ? 0.95 : 0.5);
|
||||||
|
this.trackSortie(this.add.text(slotX + 28, slotY + 3, `${slot.label} ${slot.unitNames.length}`, this.textStyle(10, filled ? '#f2e3bf' : '#9fb0bf', true))).setDepth(depth + 1);
|
||||||
|
this.trackSortie(this.add.text(slotX + 76, slotY + 3, this.compactText(slot.unitNames.join(', ') || slot.description, 12), this.textStyle(10, filled ? '#c8d2dd' : '#68727e'))).setDepth(depth + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.renderSortieFocusPanel(x + 18, y + 190, width - 36, 86, depth + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderSortieFocusPanel(x: number, y: number, width: number, height: number, depth: number) {
|
private renderSortieFocusPanel(x: number, y: number, width: number, height: number, depth: number) {
|
||||||
@@ -11459,7 +11540,6 @@ export class CampScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const status = this.unitRosterStatus(unit);
|
const status = this.unitRosterStatus(unit);
|
||||||
const summary = this.sortieUnitTacticalSummary(unit);
|
|
||||||
const recommendation = this.sortieRecommendation(unit.id);
|
const recommendation = this.sortieRecommendation(unit.id);
|
||||||
const roleLabel = sortieFormationSlotDefinitions.find((slot) => slot.role === this.sortieFormationRole(unit))?.label ?? '예비';
|
const roleLabel = sortieFormationSlotDefinitions.find((slot) => slot.role === this.sortieFormationRole(unit))?.label ?? '예비';
|
||||||
const terrainScore = this.sortieTerrainScore(unit, this.nextSortieScenario());
|
const terrainScore = this.sortieTerrainScore(unit, this.nextSortieScenario());
|
||||||
@@ -11475,24 +11555,92 @@ export class CampScene extends Phaser.Scene {
|
|||||||
.join(' / ');
|
.join(' / ');
|
||||||
const reason = recommendation?.reason ?? (this.isSortieSelected(unit.id) ? '현재 출전 편성에 포함되어 있습니다.' : '대기 훈련으로 성장시키거나 전장 역할에 맞춰 교체할 수 있습니다.');
|
const reason = recommendation?.reason ?? (this.isSortieSelected(unit.id) ? '현재 출전 편성에 포함되어 있습니다.' : '대기 훈련으로 성장시키거나 전장 역할에 맞춰 교체할 수 있습니다.');
|
||||||
|
|
||||||
this.trackSortie(this.add.text(x + 12, y + 7, '선택 무장 분석', this.textStyle(11, '#d8b15f', true))).setDepth(depth + 1);
|
this.renderSortieBattleUiIcon(this.unitClassIconKey(unit), x + 24, y + 25, 32, depth + 1, 0.98);
|
||||||
this.trackSortie(this.add.text(x + 104, y + 6, this.compactText(headline, 24), this.textStyle(13, '#f2e3bf', true))).setDepth(depth + 1);
|
this.trackSortie(this.add.text(x + 48, y + 6, this.compactText(headline, 24), this.textStyle(13, '#f2e3bf', true))).setDepth(depth + 1);
|
||||||
this.trackSortie(this.add.text(x + width - 12, y + 7, status.label, this.textStyle(11, statusColor, true))).setOrigin(1, 0).setDepth(depth + 1);
|
this.trackSortie(this.add.text(x + width - 12, y + 7, status.label, this.textStyle(11, statusColor, true))).setOrigin(1, 0).setDepth(depth + 1);
|
||||||
this.trackSortie(this.add.text(x + 12, y + 24, this.compactText(`${summary.statLine} · 지형 ${terrainGrade}(${terrainScore})`, 44), this.textStyle(10, '#c8d2dd'))).setDepth(depth + 1);
|
|
||||||
this.trackSortie(this.add.text(x + 12, y + 39, this.compactText(equipLine, 44), this.textStyle(10, '#c8d2dd'))).setDepth(depth + 1);
|
this.renderSortieStatChip('might', '무', unit.stats.might, x + 48, y + 27, 50, depth + 1);
|
||||||
this.trackSortie(this.add.text(x + 12, y + 54, this.compactText(summary.bondLine, 44), this.textStyle(10, '#a8ffd0', true))).setDepth(depth + 1);
|
this.renderSortieStatChip('intelligence', '지', unit.stats.intelligence, x + 104, y + 27, 50, depth + 1);
|
||||||
this.trackSortie(this.add.text(x + 12, y + height - 13, this.compactText(reason, 44), this.textStyle(10, recommendation ? '#ffdf7b' : '#9fb0bf', Boolean(recommendation)))).setDepth(depth + 1);
|
this.renderSortieStatChip('leadership', '통', unit.stats.leadership, x + 160, y + 27, 50, depth + 1);
|
||||||
|
this.renderSortieStatChip('move', '이', unit.move, x + 216, y + 27, 50, depth + 1);
|
||||||
|
this.trackSortie(this.add.text(x + 274, y + 30, `지형 ${terrainGrade} ${terrainScore}`, this.textStyle(10, '#d8b15f', true))).setDepth(depth + 1);
|
||||||
|
|
||||||
|
this.renderSortieEquipmentIcons(unit, x + 26, y + 52, true, depth + 1, 20, 16, 25);
|
||||||
|
this.trackSortie(this.add.text(x + 94, y + 47, this.compactText(equipLine, 38), this.textStyle(10, '#c8d2dd', true))).setDepth(depth + 1);
|
||||||
|
this.trackSortie(
|
||||||
|
this.add.text(x + 94, y + 61, this.compactText(reason, 28), this.textStyle(9, recommendation ? '#ffdf7b' : '#9fb0bf', Boolean(recommendation)))
|
||||||
|
).setDepth(depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderSortieEquipmentIcons(unit: UnitData, x: number, y: number, selected: boolean, depth: number) {
|
private renderSortieBattleUiIcon(iconKey: BattleUiIconKey, x: number, y: number, size: number, depth: number, alpha = 1) {
|
||||||
|
if (!this.textures.exists(battleUiIconTextureKey)) {
|
||||||
|
const fallback = this.trackSortie(this.add.circle(x, y, size / 2, palette.gold, 0.18));
|
||||||
|
fallback.setDepth(depth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = this.trackSortie(this.add.image(x, y, battleUiIconTextureKey, battleUiIconFrames[iconKey]));
|
||||||
|
icon.setDisplaySize(size, size);
|
||||||
|
icon.setAlpha(alpha);
|
||||||
|
icon.setDepth(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unitClassIconKey(unit: UnitData): BattleUiIconKey {
|
||||||
|
switch (unit.classKey) {
|
||||||
|
case 'lord':
|
||||||
|
return 'leadership';
|
||||||
|
case 'infantry':
|
||||||
|
return 'armor';
|
||||||
|
case 'spearman':
|
||||||
|
return 'spear';
|
||||||
|
case 'cavalry':
|
||||||
|
return 'horse';
|
||||||
|
case 'archer':
|
||||||
|
return 'bow';
|
||||||
|
case 'strategist':
|
||||||
|
return 'strategy';
|
||||||
|
case 'quartermaster':
|
||||||
|
return 'heal';
|
||||||
|
case 'rebelLeader':
|
||||||
|
return 'axe';
|
||||||
|
case 'bandit':
|
||||||
|
case 'yellowTurban':
|
||||||
|
default:
|
||||||
|
return 'sword';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formationRoleIconKey(role: SortieFormationRole): BattleUiIconKey {
|
||||||
|
if (role === 'front') {
|
||||||
|
return 'armor';
|
||||||
|
}
|
||||||
|
if (role === 'flank') {
|
||||||
|
return 'horse';
|
||||||
|
}
|
||||||
|
if (role === 'support') {
|
||||||
|
return 'strategy';
|
||||||
|
}
|
||||||
|
return 'focus';
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSortieStatChip(iconKey: BattleUiIconKey, label: string, value: number, x: number, y: number, width: number, depth: number) {
|
||||||
|
const bg = this.trackSortie(this.add.rectangle(x, y, width, 17, 0x0a1017, 0.84));
|
||||||
|
bg.setOrigin(0);
|
||||||
|
bg.setDepth(depth);
|
||||||
|
bg.setStrokeStyle(1, 0x53606c, 0.34);
|
||||||
|
this.renderSortieBattleUiIcon(iconKey, x + 9, y + 8, 14, depth + 1, 0.9);
|
||||||
|
this.trackSortie(this.add.text(x + 18, y + 3, `${label}${value}`, this.textStyle(9, '#f2e3bf', true))).setDepth(depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSortieEquipmentIcons(unit: UnitData, x: number, y: number, selected: boolean, depth: number, frameSize = 18, iconSize = 14, spacing = 23) {
|
||||||
equipmentSlots.forEach((slot, index) => {
|
equipmentSlots.forEach((slot, index) => {
|
||||||
const state = unit.equipment[slot];
|
const state = unit.equipment[slot];
|
||||||
const item = getItem(state.itemId);
|
const item = getItem(state.itemId);
|
||||||
const iconFrame = this.trackSortie(this.add.rectangle(x + index * 23, y, 18, 18, selected && item.rank === 'treasure' ? 0x2c2630 : 0x0a1017, 0.92));
|
const iconFrame = this.trackSortie(this.add.rectangle(x + index * spacing, y, frameSize, frameSize, selected && item.rank === 'treasure' ? 0x2c2630 : 0x0a1017, 0.92));
|
||||||
iconFrame.setStrokeStyle(1, item.rank === 'treasure' ? palette.gold : 0x53606c, selected ? 0.62 : 0.34);
|
iconFrame.setStrokeStyle(1, item.rank === 'treasure' ? palette.gold : 0x53606c, selected ? 0.62 : 0.34);
|
||||||
iconFrame.setDepth(depth);
|
iconFrame.setDepth(depth);
|
||||||
const icon = this.trackSortie(this.add.image(x + index * 23, y, `item-${item.id}`));
|
const icon = this.trackSortie(this.add.image(x + index * spacing, y, `item-${item.id}`));
|
||||||
icon.setDisplaySize(14, 14);
|
icon.setDisplaySize(iconSize, iconSize);
|
||||||
icon.setAlpha(selected ? 1 : 0.52);
|
icon.setAlpha(selected ? 1 : 0.52);
|
||||||
icon.setDepth(depth + 1);
|
icon.setDepth(depth + 1);
|
||||||
});
|
});
|
||||||
@@ -11903,11 +12051,14 @@ export class CampScene extends Phaser.Scene {
|
|||||||
const focus = this.reserveTrainingFocusDefinition();
|
const focus = this.reserveTrainingFocusDefinition();
|
||||||
const reserveCount = this.sortiePlanSummary().reserveCount;
|
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 + 12, y + 7, '대기 훈련', this.textStyle(12, '#f2e3bf', true))).setDepth(depth + 1);
|
||||||
|
const focusPreview = reserveCount > 0
|
||||||
|
? `경험 +${focus.expGained} · 장비 +${focus.equipmentExpGained}${focus.bondExpGained ? ` · 공명 +${focus.bondExpGained}` : ''}`
|
||||||
|
: '대기 없음';
|
||||||
this.trackSortie(
|
this.trackSortie(
|
||||||
this.add.text(
|
this.add.text(
|
||||||
x + width - 12,
|
x + width - 12,
|
||||||
y + 7,
|
y + 7,
|
||||||
reserveCount > 0 ? `${reserveCount}명 · ${focus.label}` : '대기 없음',
|
reserveCount > 0 ? `${reserveCount}명 · ${focusPreview}` : focusPreview,
|
||||||
this.textStyle(11, reserveCount > 0 ? '#d8b15f' : '#9fb0bf', true)
|
this.textStyle(11, reserveCount > 0 ? '#d8b15f' : '#9fb0bf', true)
|
||||||
)
|
)
|
||||||
).setOrigin(1, 0).setDepth(depth + 1);
|
).setOrigin(1, 0).setDepth(depth + 1);
|
||||||
@@ -11916,14 +12067,15 @@ export class CampScene extends Phaser.Scene {
|
|||||||
campaignReserveTrainingFocusDefinitions.forEach((option, index) => {
|
campaignReserveTrainingFocusDefinitions.forEach((option, index) => {
|
||||||
const buttonX = x + 12 + index * (buttonWidth + 8);
|
const buttonX = x + 12 + index * (buttonWidth + 8);
|
||||||
const selected = option.id === focus.id;
|
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));
|
const button = this.trackSortie(this.add.rectangle(buttonX, y + 28, buttonWidth, 20, selected ? 0x263a2d : 0x0e151d, selected ? 0.98 : 0.86));
|
||||||
button.setOrigin(0);
|
button.setOrigin(0);
|
||||||
button.setDepth(depth + 1);
|
button.setDepth(depth + 1);
|
||||||
button.setStrokeStyle(1, selected ? palette.green : 0x53606c, selected ? 0.78 : 0.42);
|
button.setStrokeStyle(1, selected ? palette.green : 0x53606c, selected ? 0.78 : 0.42);
|
||||||
button.setInteractive({ useHandCursor: true });
|
button.setInteractive({ useHandCursor: true });
|
||||||
button.on('pointerdown', () => this.setReserveTrainingFocus(option.id));
|
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)));
|
const optionText = `${option.label} ${option.expGained}/${option.equipmentExpGained}${option.bondExpGained ? `/${option.bondExpGained}` : ''}`;
|
||||||
|
const label = this.trackSortie(this.add.text(buttonX + buttonWidth / 2, y + 33, this.compactText(optionText, 10), this.textStyle(9, selected ? '#a8ffd0' : '#c8d2dd', true)));
|
||||||
label.setOrigin(0.5, 0);
|
label.setOrigin(0.5, 0);
|
||||||
label.setDepth(depth + 2);
|
label.setDepth(depth + 2);
|
||||||
label.setInteractive({ useHandCursor: true });
|
label.setInteractive({ useHandCursor: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user