feat: polish camp and progression interactions
This commit is contained in:
@@ -240,6 +240,15 @@ type CampTabButtonView = {
|
||||
tab: CampTab;
|
||||
bg: Phaser.GameObjects.Rectangle;
|
||||
text: Phaser.GameObjects.Text;
|
||||
indicator: Phaser.GameObjects.Rectangle;
|
||||
hovered: boolean;
|
||||
};
|
||||
|
||||
type CampActionButtonView = {
|
||||
background: Phaser.GameObjects.Rectangle;
|
||||
label: Phaser.GameObjects.Text;
|
||||
primary: boolean;
|
||||
hovered: boolean;
|
||||
};
|
||||
|
||||
type CampRosterBounds = {
|
||||
@@ -11247,6 +11256,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private reportFormationHistoryView?: ReportFormationHistoryView;
|
||||
private reportFormationHistoryUndoState?: ReportFormationHistoryUndoState;
|
||||
private tabButtons: CampTabButtonView[] = [];
|
||||
private sortiePrimaryActionButton?: CampActionButtonView;
|
||||
private visitedTabs = new Set<CampTab>();
|
||||
private selectedSortieUnitIds: string[] = [];
|
||||
private sortieFormationAssignments: SortieFormationAssignments = {};
|
||||
@@ -11355,6 +11365,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.reportFormationHistoryView = undefined;
|
||||
this.reportFormationHistoryUndoState = undefined;
|
||||
this.tabButtons = [];
|
||||
this.sortiePrimaryActionButton = undefined;
|
||||
this.visitedTabs = new Set(['status']);
|
||||
this.campaign = getCampaignState();
|
||||
this.report = this.campaign.firstBattleReport ?? getFirstBattleReport() ?? this.createFallbackReport();
|
||||
@@ -12594,7 +12605,7 @@ export class CampScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
this.showSortiePrep();
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
private showCampSaveSlotPanel() {
|
||||
@@ -12938,11 +12949,15 @@ export class CampScene extends Phaser.Scene {
|
||||
const bg = this.scaleLegacyCampUi(this.add.rectangle(x, y, 76, 34, 0x182431, 0.94));
|
||||
bg.setStrokeStyle(1, palette.blue, 0.62);
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
bg.on('pointerdown', () => {
|
||||
|
||||
const indicator = this.scaleLegacyCampUi(this.add.rectangle(x, y + 14, 52, 3, palette.gold, 1));
|
||||
indicator.setVisible(false);
|
||||
|
||||
const selectTab = () => {
|
||||
soundDirector.playSelect();
|
||||
this.activeTab = tab;
|
||||
this.render();
|
||||
});
|
||||
};
|
||||
|
||||
const text = this.scaleLegacyCampUi(this.add.text(x, y, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
@@ -12952,42 +12967,58 @@ export class CampScene extends Phaser.Scene {
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerdown', () => {
|
||||
soundDirector.playSelect();
|
||||
this.activeTab = tab;
|
||||
this.render();
|
||||
const view: CampTabButtonView = { tab, bg, text, indicator, hovered: false };
|
||||
const setHovered = (hovered: boolean) => {
|
||||
view.hovered = hovered;
|
||||
this.updateTabButtons();
|
||||
};
|
||||
[bg, text].forEach((target) => {
|
||||
target.on('pointerover', () => setHovered(true));
|
||||
target.on('pointerout', () => setHovered(false));
|
||||
target.on('pointerdown', selectTab);
|
||||
});
|
||||
this.tabButtons.push({ tab, bg, text });
|
||||
this.tabButtons.push(view);
|
||||
this.updateTabButtons();
|
||||
}
|
||||
|
||||
private updateTabButtons() {
|
||||
const accentColor = this.campSkinSelection?.skin.accentColor ?? palette.gold;
|
||||
this.tabButtons.forEach(({ tab, bg, text }) => {
|
||||
this.tabButtons.forEach(({ tab, bg, text, indicator, hovered }) => {
|
||||
const active = this.activeTab === tab;
|
||||
bg.setFillStyle(active ? 0x25384a : 0x182431, active ? 0.98 : 0.94);
|
||||
bg.setStrokeStyle(1, active ? accentColor : palette.blue, active ? 0.9 : 0.62);
|
||||
text.setColor(active ? '#fff2b8' : '#f2e3bf');
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private addCommandButton(label: string, x: number, y: number, width: number, action: () => void) {
|
||||
const bg = this.scaleLegacyCampUi(this.add.rectangle(x, y, width, 36, 0x1a2630, 0.96));
|
||||
bg.setStrokeStyle(1, this.campSkinSelection?.skin.accentColor ?? palette.gold, 0.84);
|
||||
private addCommandButton(label: string, x: number, y: number, width: number, action: () => void, primary = false) {
|
||||
const restingFill = primary ? 0x4a371d : 0x1a2630;
|
||||
const hoverFill = primary ? 0x654c25 : 0x283947;
|
||||
const accentColor = this.campSkinSelection?.skin.accentColor ?? palette.gold;
|
||||
const bg = this.scaleLegacyCampUi(this.add.rectangle(x, y, width, 36, restingFill, 0.96));
|
||||
bg.setStrokeStyle(primary ? 2 : 1, accentColor, primary ? 0.96 : 0.84);
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
bg.on('pointerover', () => bg.setFillStyle(0x283947, 0.98));
|
||||
bg.on('pointerout', () => bg.setFillStyle(0x1a2630, 0.96));
|
||||
bg.on('pointerdown', action);
|
||||
|
||||
const text = this.scaleLegacyCampUi(this.add.text(x, y, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: '#f2e3bf',
|
||||
color: primary ? '#fff2b8' : '#f2e3bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerdown', action);
|
||||
const setHovered = (hovered: boolean) => {
|
||||
bg.setFillStyle(hovered ? hoverFill : restingFill, hovered ? 0.99 : 0.96);
|
||||
text.setColor(hovered || primary ? '#fff2b8' : '#f2e3bf');
|
||||
};
|
||||
[bg, text].forEach((target) => {
|
||||
target.on('pointerover', () => setHovered(true));
|
||||
target.on('pointerout', () => setHovered(false));
|
||||
target.on('pointerdown', action);
|
||||
});
|
||||
}
|
||||
|
||||
private render() {
|
||||
@@ -15077,10 +15108,12 @@ export class CampScene extends Phaser.Scene {
|
||||
bg.setOrigin(0);
|
||||
bg.setDepth(depth);
|
||||
bg.setStrokeStyle(1, active ? palette.green : enabled ? palette.gold : 0x53606c, active ? 0.78 : enabled ? 0.56 : 0.3);
|
||||
const hoverFill = active ? 0x314738 : 0x283947;
|
||||
const setHovered = (hovered: boolean) => bg.setFillStyle(hovered ? hoverFill : fill, hovered ? 0.98 : 0.96);
|
||||
if (enabled) {
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
bg.on('pointerover', () => bg.setFillStyle(active ? 0x314738 : 0x283947, 0.98));
|
||||
bg.on('pointerout', () => bg.setFillStyle(fill, 0.96));
|
||||
bg.on('pointerover', () => setHovered(true));
|
||||
bg.on('pointerout', () => setHovered(false));
|
||||
bg.on('pointerdown', action);
|
||||
}
|
||||
const text = this.trackSortie(this.add.text(x + width / 2, y + height / 2, label, this.textStyle(12, enabled || active ? '#f2e3bf' : '#77818c', true)));
|
||||
@@ -15088,6 +15121,8 @@ export class CampScene extends Phaser.Scene {
|
||||
text.setDepth(depth + 1);
|
||||
if (enabled) {
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerover', () => setHovered(true));
|
||||
text.on('pointerout', () => setHovered(false));
|
||||
text.on('pointerdown', action);
|
||||
}
|
||||
}
|
||||
@@ -18946,19 +18981,32 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private addSortieButton(label: string, x: number, y: number, width: number, action: () => void, depth: number, primary = false) {
|
||||
const bg = this.trackSortie(this.add.rectangle(x, y, width, 36, 0x1a2630, 0.96));
|
||||
const restingFill = primary ? 0x4a371d : 0x1a2630;
|
||||
const hoverFill = primary ? 0x654c25 : 0x283947;
|
||||
const bg = this.trackSortie(this.add.rectangle(x, y, width, 36, restingFill, 0.96));
|
||||
bg.setDepth(depth);
|
||||
bg.setStrokeStyle(1, primary ? palette.gold : palette.blue, 0.78);
|
||||
bg.setStrokeStyle(primary ? 2 : 1, primary ? palette.gold : palette.blue, primary ? 0.96 : 0.78);
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
bg.on('pointerover', () => bg.setFillStyle(0x283947, 0.98));
|
||||
bg.on('pointerout', () => bg.setFillStyle(0x1a2630, 0.96));
|
||||
bg.on('pointerdown', action);
|
||||
|
||||
const text = this.trackSortie(this.add.text(x, y, label, this.textStyle(15, '#f2e3bf', true)));
|
||||
const text = this.trackSortie(this.add.text(x, y, label, this.textStyle(15, primary ? '#fff2b8' : '#f2e3bf', true)));
|
||||
text.setOrigin(0.5);
|
||||
text.setDepth(depth + 1);
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerdown', action);
|
||||
const view: CampActionButtonView = { background: bg, label: text, primary, hovered: false };
|
||||
const setHovered = (hovered: boolean) => {
|
||||
view.hovered = hovered;
|
||||
bg.setFillStyle(hovered ? hoverFill : restingFill, hovered ? 0.99 : 0.96);
|
||||
text.setColor(hovered || primary ? '#fff2b8' : '#f2e3bf');
|
||||
};
|
||||
[bg, text].forEach((target) => {
|
||||
target.on('pointerover', () => setHovered(true));
|
||||
target.on('pointerout', () => setHovered(false));
|
||||
target.on('pointerdown', action);
|
||||
});
|
||||
if (primary) {
|
||||
this.sortiePrimaryActionButton = view;
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
private startVictoryStory() {
|
||||
@@ -19072,6 +19120,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private hideSortiePrep(clearPinnedSwap = true) {
|
||||
this.sortieObjects.forEach((object) => object.destroy());
|
||||
this.sortieObjects = [];
|
||||
this.sortiePrimaryActionButton = undefined;
|
||||
this.sortiePortraitRosterLayout = undefined;
|
||||
this.sortieHoveredUnitId = undefined;
|
||||
if (clearPinnedSwap) {
|
||||
@@ -19139,11 +19188,24 @@ export class CampScene extends Phaser.Scene {
|
||||
const y = listY + index * rowGap;
|
||||
const active = this.selectedUnitId === unit.id;
|
||||
const rosterStatus = this.unitRosterStatus(unit);
|
||||
const disabled = rosterStatus.tone === 'disabled';
|
||||
const training = this.latestReserveTraining(unit.id);
|
||||
const bg = this.track(this.add.rectangle(x, y, width, cardHeight, active ? 0x25384a : 0x111922, active ? 0.96 : 0.86));
|
||||
const restingFill = active ? 0x25384a : disabled ? 0x0d131a : 0x111922;
|
||||
const restingAlpha = active ? 0.96 : disabled ? 0.72 : 0.86;
|
||||
const restingStroke = active ? palette.gold : disabled ? 0x53606c : palette.blue;
|
||||
const restingStrokeAlpha = active ? 0.9 : disabled ? 0.42 : 0.46;
|
||||
const bg = this.track(this.add.rectangle(x, y, width, cardHeight, restingFill, restingAlpha));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, active ? palette.gold : palette.blue, active ? 0.9 : 0.46);
|
||||
bg.setStrokeStyle(2, restingStroke, restingStrokeAlpha);
|
||||
bg.setInteractive({ useHandCursor: true });
|
||||
bg.on('pointerover', () => {
|
||||
bg.setFillStyle(active ? 0x31495d : disabled ? 0x182029 : 0x1c2a36, active || !disabled ? 0.98 : 0.82);
|
||||
bg.setStrokeStyle(2, active || !disabled ? palette.gold : 0x71808e, active ? 0.98 : disabled ? 0.62 : 0.88);
|
||||
});
|
||||
bg.on('pointerout', () => {
|
||||
bg.setFillStyle(restingFill, restingAlpha);
|
||||
bg.setStrokeStyle(2, restingStroke, restingStrokeAlpha);
|
||||
});
|
||||
bg.on('pointerdown', () => {
|
||||
soundDirector.playSelect();
|
||||
this.selectedUnitId = unit.id;
|
||||
@@ -22669,6 +22731,18 @@ export class CampScene extends Phaser.Scene {
|
||||
return {
|
||||
scene: this.scene.key,
|
||||
activeTab: this.activeTab,
|
||||
campTabs: this.tabButtons.map((button) => ({
|
||||
id: button.tab,
|
||||
active: this.activeTab === button.tab,
|
||||
hovered: button.hovered,
|
||||
visualState: this.activeTab === button.tab ? 'active' : button.hovered ? 'hover' : 'idle',
|
||||
bounds: this.sortieObjectBoundsDebug(button.bg),
|
||||
interactive: Boolean(button.bg.input?.enabled && button.text.input?.enabled),
|
||||
indicatorVisible: button.indicator.visible,
|
||||
fillColor: button.bg.fillColor,
|
||||
strokeColor: button.bg.strokeColor,
|
||||
lineWidth: button.bg.lineWidth
|
||||
})),
|
||||
campRoster: this.campRosterLayout
|
||||
? {
|
||||
...this.campRosterLayout,
|
||||
@@ -23032,11 +23106,21 @@ export class CampScene extends Phaser.Scene {
|
||||
active: step.id === this.sortiePrepStep,
|
||||
complete: this.firstSortieStepComplete(step.id, sortieChecklist)
|
||||
})),
|
||||
sortiePrimaryAction: stagedSortiePrep
|
||||
sortiePrimaryAction: this.sortiePrimaryActionButton?.background.active
|
||||
? {
|
||||
kind: nextSortiePrepStep ? 'next' : 'launch',
|
||||
nextStep: nextSortiePrepStep,
|
||||
label: nextSortiePrepStep === 'formation' ? '편성하기' : nextSortiePrepStep === 'loadout' ? '장비·보급' : '출진'
|
||||
label: this.sortiePrimaryActionButton.label.text,
|
||||
primary: this.sortiePrimaryActionButton.primary,
|
||||
hovered: this.sortiePrimaryActionButton.hovered,
|
||||
visualState: this.sortiePrimaryActionButton.hovered ? 'hover' : 'primary',
|
||||
bounds: this.sortieObjectBoundsDebug(this.sortiePrimaryActionButton.background),
|
||||
interactive: Boolean(
|
||||
this.sortiePrimaryActionButton.background.input?.enabled && this.sortiePrimaryActionButton.label.input?.enabled
|
||||
),
|
||||
fillColor: this.sortiePrimaryActionButton.background.fillColor,
|
||||
strokeColor: this.sortiePrimaryActionButton.background.strokeColor,
|
||||
lineWidth: this.sortiePrimaryActionButton.background.lineWidth
|
||||
}
|
||||
: null,
|
||||
sortiePortraitRosterView: {
|
||||
|
||||
Reference in New Issue
Block a user