feat: refine battle feedback and early guidance
This commit is contained in:
@@ -774,7 +774,7 @@ const campSupplies: CampSupplyDefinition[] = [
|
||||
label: '탁주',
|
||||
usableId: 'wine',
|
||||
title: '탁주',
|
||||
description: '선택 장수의 병력을 8 회복하고 연결된 공명 경험치를 2 올립니다.',
|
||||
description: '선택 장수의 병력을 8 회복하고 연결된 모든 공명 경험치를 각각 2 올립니다.',
|
||||
healHp: 8,
|
||||
bondExp: 2
|
||||
},
|
||||
@@ -789,6 +789,7 @@ const campSupplies: CampSupplyDefinition[] = [
|
||||
];
|
||||
|
||||
const campSupplyByLabel = new Map(campSupplies.map((supply) => [supply.label, supply]));
|
||||
const equipmentInventoryPageSize = 4;
|
||||
|
||||
const merchantItems: MerchantItemDefinition[] = [
|
||||
{
|
||||
@@ -11234,6 +11235,15 @@ export class CampScene extends Phaser.Scene {
|
||||
private activeTab: CampTab = 'status';
|
||||
private selectedDialogueId = campDialogues[0].id;
|
||||
private selectedVisitId = campVisits[0].id;
|
||||
private equipmentInventoryPage = 0;
|
||||
private equipmentPanelBackground?: Phaser.GameObjects.Rectangle;
|
||||
private equipmentInventoryPreviousButton?: Phaser.GameObjects.Rectangle;
|
||||
private equipmentInventoryNextButton?: Phaser.GameObjects.Rectangle;
|
||||
private equipmentInventoryVisibleItemIds: string[] = [];
|
||||
private suppliesPanelBackground?: Phaser.GameObjects.Rectangle;
|
||||
private suppliesEquipmentBoxes: Phaser.GameObjects.Rectangle[] = [];
|
||||
private suppliesTrophyBoxes: Phaser.GameObjects.Rectangle[] = [];
|
||||
private reportPanelBackground?: Phaser.GameObjects.Rectangle;
|
||||
private contentObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private dialogueObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private sortieObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
@@ -19409,6 +19419,7 @@ export class CampScene extends Phaser.Scene {
|
||||
const x = 42;
|
||||
const y = 590;
|
||||
const bg = this.track(this.add.rectangle(x, y, 1180, 84, 0x101820, 0.86));
|
||||
this.reportPanelBackground = bg;
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, palette.gold, 0.48);
|
||||
this.track(this.add.text(x + 18, y + 14, '전투 보고', this.textStyle(18, '#f2e3bf', true)));
|
||||
@@ -21470,6 +21481,9 @@ export class CampScene extends Phaser.Scene {
|
||||
const width = 828;
|
||||
const height = 464;
|
||||
const bg = this.track(this.add.rectangle(x, y, width, height, 0x101820, 0.91));
|
||||
this.equipmentPanelBackground = bg;
|
||||
this.equipmentInventoryPreviousButton = undefined;
|
||||
this.equipmentInventoryNextButton = undefined;
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, palette.gold, 0.58);
|
||||
|
||||
@@ -21483,7 +21497,39 @@ export class CampScene extends Phaser.Scene {
|
||||
});
|
||||
|
||||
const inventoryEntries = this.equipmentInventoryEntries();
|
||||
this.track(this.add.text(x + 24, y + 266, `보유 장비 ${inventoryEntries.length}`, this.textStyle(18, '#f2e3bf', true)));
|
||||
const inventoryPageCount = Math.max(1, Math.ceil(inventoryEntries.length / equipmentInventoryPageSize));
|
||||
this.equipmentInventoryPage = Phaser.Math.Clamp(this.equipmentInventoryPage, 0, inventoryPageCount - 1);
|
||||
const inventoryStart = this.equipmentInventoryPage * equipmentInventoryPageSize;
|
||||
const visibleInventoryEntries = inventoryEntries.slice(inventoryStart, inventoryStart + equipmentInventoryPageSize);
|
||||
this.equipmentInventoryVisibleItemIds = visibleInventoryEntries.map((entry) => entry.item.id);
|
||||
this.track(this.add.text(
|
||||
x + 24,
|
||||
y + 266,
|
||||
`보유 장비 ${inventoryEntries.length}${inventoryPageCount > 1 ? ` · ${this.equipmentInventoryPage + 1}/${inventoryPageCount}` : ''}`,
|
||||
this.textStyle(18, '#f2e3bf', true)
|
||||
));
|
||||
if (inventoryPageCount > 1) {
|
||||
this.equipmentInventoryPreviousButton = this.renderEquipmentInventoryPageButton(
|
||||
x + 424,
|
||||
y + 278,
|
||||
'‹',
|
||||
this.equipmentInventoryPage > 0,
|
||||
() => {
|
||||
this.equipmentInventoryPage -= 1;
|
||||
this.render();
|
||||
}
|
||||
);
|
||||
this.equipmentInventoryNextButton = this.renderEquipmentInventoryPageButton(
|
||||
x + 462,
|
||||
y + 278,
|
||||
'›',
|
||||
this.equipmentInventoryPage < inventoryPageCount - 1,
|
||||
() => {
|
||||
this.equipmentInventoryPage += 1;
|
||||
this.render();
|
||||
}
|
||||
);
|
||||
}
|
||||
this.track(this.add.text(x + 516, y + 268, '장비 효과 요약', this.textStyle(17, '#f2e3bf', true)));
|
||||
|
||||
if (inventoryEntries.length === 0) {
|
||||
@@ -21493,7 +21539,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.track(this.add.text(x + 44, y + 324, '교체 가능한 보유 장비가 없습니다.', this.textStyle(15, '#9fb0bf', true)));
|
||||
this.track(this.add.text(x + 44, y + 352, '전투 보상이나 군영 상인을 통해 장비를 확보하면 이곳에서 비교할 수 있습니다.', this.textStyle(12, '#77818c')));
|
||||
} else {
|
||||
inventoryEntries.slice(0, 4).forEach((entry, index) => {
|
||||
visibleInventoryEntries.forEach((entry, index) => {
|
||||
this.renderEquipmentCompareRow(entry, unit, x + 24, y + 298 + index * 40, 468, 36);
|
||||
});
|
||||
}
|
||||
@@ -21502,6 +21548,31 @@ export class CampScene extends Phaser.Scene {
|
||||
this.renderEquipmentInventorySummary(inventoryEntries, x + 516, y + 432, 280);
|
||||
}
|
||||
|
||||
private renderEquipmentInventoryPageButton(
|
||||
x: number,
|
||||
y: number,
|
||||
label: string,
|
||||
enabled: boolean,
|
||||
action: () => void
|
||||
) {
|
||||
const bg = this.track(this.add.rectangle(x, y, 30, 24, enabled ? 0x1a2630 : 0x111820, enabled ? 0.96 : 0.68));
|
||||
bg.setStrokeStyle(1, enabled ? palette.gold : 0x53606c, enabled ? 0.72 : 0.34);
|
||||
const text = this.track(this.add.text(x, y - 1, label, this.textStyle(18, enabled ? '#f2e3bf' : '#6f7882', true)));
|
||||
text.setOrigin(0.5);
|
||||
if (!enabled) {
|
||||
return bg;
|
||||
}
|
||||
|
||||
const setHovered = (hovered: boolean) => bg.setFillStyle(hovered ? 0x654c25 : 0x1a2630, hovered ? 0.99 : 0.96);
|
||||
[bg, text].forEach((target) => {
|
||||
target.setInteractive({ useHandCursor: true });
|
||||
target.on('pointerover', () => setHovered(true));
|
||||
target.on('pointerout', () => setHovered(false));
|
||||
target.on('pointerdown', action);
|
||||
});
|
||||
return bg;
|
||||
}
|
||||
|
||||
private renderEquipmentUnitSummary(unit: UnitData, x: number, y: number, width: number, height: number) {
|
||||
const bg = this.track(this.add.rectangle(x, y, width, height, 0x0d141c, 0.92));
|
||||
bg.setOrigin(0);
|
||||
@@ -21611,7 +21682,10 @@ export class CampScene extends Phaser.Scene {
|
||||
private renderSuppliesPanel() {
|
||||
const x = 394;
|
||||
const y = 120;
|
||||
const bg = this.track(this.add.rectangle(x, y, 828, 444, 0x101820, 0.9));
|
||||
const bg = this.track(this.add.rectangle(x, y, 828, 466, 0x101820, 0.9));
|
||||
this.suppliesPanelBackground = bg;
|
||||
this.suppliesEquipmentBoxes = [];
|
||||
this.suppliesTrophyBoxes = [];
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, palette.blue, 0.56);
|
||||
this.track(this.add.text(x + 24, y + 22, '정비와 보급', this.textStyle(24, '#f2e3bf', true)));
|
||||
@@ -21628,23 +21702,23 @@ export class CampScene extends Phaser.Scene {
|
||||
|
||||
this.renderMerchantPanel(x + 24, y + 202, 342);
|
||||
|
||||
this.track(this.add.text(x + 390, y + 318, '보유 장비', this.textStyle(18, '#f2e3bf', true)));
|
||||
this.track(this.add.text(x + 390, y + 312, '보유 장비', this.textStyle(18, '#f2e3bf', true)));
|
||||
const equipmentEntries = this.equipmentInventoryEntries();
|
||||
if (equipmentEntries.length === 0) {
|
||||
this.track(this.add.text(x + 390, y + 346, '교체 가능한 장비 없음', this.textStyle(12, '#9fb0bf')));
|
||||
} else {
|
||||
equipmentEntries.slice(0, 3).forEach((entry, index) => {
|
||||
this.renderEquipmentInventoryBox(entry, x + 390 + index * 128, y + 344);
|
||||
this.suppliesEquipmentBoxes.push(this.renderEquipmentInventoryBox(entry, x + 390 + index * 128, y + 336));
|
||||
});
|
||||
}
|
||||
|
||||
this.track(this.add.text(x + 390, y + 410, '전리품과 명성', this.textStyle(17, '#f2e3bf', true)));
|
||||
this.track(this.add.text(x + 390, y + 394, '전리품과 명성', this.textStyle(17, '#f2e3bf', true)));
|
||||
const trophies = this.nonEquipmentInventoryLabels();
|
||||
if (trophies.length === 0) {
|
||||
this.track(this.add.text(x + 390, y + 438, '장부/명성 전리품 없음', this.textStyle(12, '#9fb0bf')));
|
||||
} else {
|
||||
trophies.slice(0, 3).forEach((reward, index) => {
|
||||
this.renderSupplyBox(reward, x + 390 + index * 128, y + 436);
|
||||
this.suppliesTrophyBoxes.push(this.renderSupplyBox(reward, x + 390 + index * 128, y + 418));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21708,6 +21782,7 @@ export class CampScene extends Phaser.Scene {
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, palette.gold, 0.52);
|
||||
this.track(this.add.text(x + 10, y + 14, this.compactText(label, 9), this.textStyle(13, '#f2e3bf', true)));
|
||||
return bg;
|
||||
}
|
||||
|
||||
private renderEquipmentInventoryBox(entry: { label: string; amount: number; item: ItemDefinition }, x: number, y: number) {
|
||||
@@ -21734,6 +21809,7 @@ export class CampScene extends Phaser.Scene {
|
||||
actionText.setInteractive({ useHandCursor: true });
|
||||
actionText.on('pointerdown', action);
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
|
||||
private renderSupplyTargetCard(unit: UnitData, x: number, y: number, width: number, height: number) {
|
||||
@@ -21823,7 +21899,7 @@ export class CampScene extends Phaser.Scene {
|
||||
soundDirector.playEffect('exp-gain', { volume: 0.24, stopAfterMs: 620 });
|
||||
|
||||
const hpText = hpGain > 0 ? `병력 +${hpGain}` : '';
|
||||
const bondText = bondGain > 0 ? `공명 +${supply.bondExp}` : '';
|
||||
const bondText = bondGain > 0 ? `연결 공명 총 +${bondGain}` : '';
|
||||
this.showCampNotice(`${target.name} ${supply.title} 사용 · ${[hpText, bondText].filter(Boolean).join(' / ')}`);
|
||||
this.render();
|
||||
}
|
||||
@@ -22052,7 +22128,8 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showCampNotice(message: string) {
|
||||
this.dialogueObjects.forEach((object) => object.destroy());
|
||||
this.tweens.killTweensOf(this.dialogueObjects);
|
||||
this.dialogueObjects.forEach((object) => object.active && object.destroy());
|
||||
this.dialogueObjects = [];
|
||||
const depth = this.sortieObjects.length > 0 ? 72 : 30;
|
||||
const noticeWidth = Math.min(640, Math.max(360, message.length * 13));
|
||||
@@ -22066,15 +22143,18 @@ export class CampScene extends Phaser.Scene {
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setDepth(depth + 1);
|
||||
this.dialogueObjects.push(box, text);
|
||||
const noticeObjects: Phaser.GameObjects.GameObject[] = [box, text];
|
||||
this.dialogueObjects = noticeObjects;
|
||||
this.tweens.add({
|
||||
targets: this.dialogueObjects,
|
||||
targets: noticeObjects,
|
||||
alpha: 0,
|
||||
delay: 1300,
|
||||
duration: 320,
|
||||
onComplete: () => {
|
||||
this.dialogueObjects.forEach((object) => object.destroy());
|
||||
this.dialogueObjects = [];
|
||||
noticeObjects.forEach((object) => object.active && object.destroy());
|
||||
if (this.dialogueObjects === noticeObjects) {
|
||||
this.dialogueObjects = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -22610,6 +22690,14 @@ export class CampScene extends Phaser.Scene {
|
||||
this.contentObjects.forEach((object) => object.destroy());
|
||||
this.contentObjects = [];
|
||||
this.campRosterLayout = undefined;
|
||||
this.equipmentPanelBackground = undefined;
|
||||
this.equipmentInventoryPreviousButton = undefined;
|
||||
this.equipmentInventoryNextButton = undefined;
|
||||
this.equipmentInventoryVisibleItemIds = [];
|
||||
this.suppliesPanelBackground = undefined;
|
||||
this.suppliesEquipmentBoxes = [];
|
||||
this.suppliesTrophyBoxes = [];
|
||||
this.reportPanelBackground = undefined;
|
||||
this.reportFormationReviewToggleButton = undefined;
|
||||
this.reportFormationHistoryToggleButton = undefined;
|
||||
this.reportSortieOrderRewardBadge = undefined;
|
||||
@@ -22728,6 +22816,8 @@ export class CampScene extends Phaser.Scene {
|
||||
1,
|
||||
Math.ceil(reportFormationHistoryRecords.length / reportFormationHistoryPageSize)
|
||||
);
|
||||
const equipmentInventory = this.equipmentInventoryEntries();
|
||||
const equipmentInventoryPageCount = Math.max(1, Math.ceil(equipmentInventory.length / equipmentInventoryPageSize));
|
||||
return {
|
||||
scene: this.scene.key,
|
||||
activeTab: this.activeTab,
|
||||
@@ -23422,7 +23512,35 @@ export class CampScene extends Phaser.Scene {
|
||||
strategyLine: summary.strategyLine
|
||||
};
|
||||
}),
|
||||
equipmentInventory: this.equipmentInventoryEntries().map((entry) => ({
|
||||
secondaryPanels: {
|
||||
report: {
|
||||
visible: Boolean(this.reportPanelBackground?.active),
|
||||
bounds: this.sortieObjectBoundsDebug(this.reportPanelBackground)
|
||||
},
|
||||
supplies: {
|
||||
visible: this.activeTab === 'supplies' && Boolean(this.suppliesPanelBackground?.active),
|
||||
bounds: this.sortieObjectBoundsDebug(this.suppliesPanelBackground),
|
||||
equipmentBoxBounds: this.suppliesEquipmentBoxes
|
||||
.map((box) => this.sortieObjectBoundsDebug(box))
|
||||
.filter((bounds) => bounds !== null),
|
||||
trophyBoxBounds: this.suppliesTrophyBoxes
|
||||
.map((box) => this.sortieObjectBoundsDebug(box))
|
||||
.filter((bounds) => bounds !== null)
|
||||
},
|
||||
equipment: {
|
||||
visible: this.activeTab === 'equipment' && Boolean(this.equipmentPanelBackground?.active),
|
||||
bounds: this.sortieObjectBoundsDebug(this.equipmentPanelBackground),
|
||||
page: this.equipmentInventoryPage,
|
||||
pageCount: equipmentInventoryPageCount,
|
||||
pageSize: equipmentInventoryPageSize,
|
||||
visibleItemIds: [...this.equipmentInventoryVisibleItemIds],
|
||||
previousButtonBounds: this.sortieObjectBoundsDebug(this.equipmentInventoryPreviousButton),
|
||||
nextButtonBounds: this.sortieObjectBoundsDebug(this.equipmentInventoryNextButton),
|
||||
previousEnabled: Boolean(this.equipmentInventoryPreviousButton?.input?.enabled),
|
||||
nextEnabled: Boolean(this.equipmentInventoryNextButton?.input?.enabled)
|
||||
}
|
||||
},
|
||||
equipmentInventory: equipmentInventory.map((entry) => ({
|
||||
label: entry.label,
|
||||
amount: entry.amount,
|
||||
itemId: entry.item.id,
|
||||
|
||||
Reference in New Issue
Block a user