Add first battle deployment phase
This commit is contained in:
@@ -1108,7 +1108,7 @@ type UnitActionPose = 'attack' | 'strategy' | 'item' | 'hurt' | 'celebrate';
|
||||
|
||||
const levelUpCelebrationStepOffsets = [0, -7, -2, -9, 0, -6, -3, -8];
|
||||
|
||||
type BattlePhase = 'idle' | 'moving' | 'command' | 'targeting' | 'animating' | 'resolved';
|
||||
type BattlePhase = 'deployment' | 'idle' | 'moving' | 'command' | 'targeting' | 'animating' | 'resolved';
|
||||
type BattleCommand = 'attack' | 'strategy' | 'item' | 'wait';
|
||||
type DamageCommand = Exclude<BattleCommand, 'wait'>;
|
||||
type UsableCommand = Extract<BattleCommand, 'strategy' | 'item'>;
|
||||
@@ -1146,6 +1146,17 @@ type TurnEndPromptOptions = {
|
||||
secondaryAction?: () => void;
|
||||
};
|
||||
|
||||
type DeploymentSlot = {
|
||||
x: number;
|
||||
y: number;
|
||||
role: SortieFormationRole;
|
||||
label: string;
|
||||
note: string;
|
||||
unitId?: string;
|
||||
icon: BattleUiIconKey;
|
||||
tone: number;
|
||||
};
|
||||
|
||||
type BattleSceneData = {
|
||||
battleId?: string;
|
||||
selectedSortieUnitIds?: string[];
|
||||
@@ -3026,6 +3037,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
private sidePanelObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private commandButtons: CommandButton[] = [];
|
||||
private commandMenuObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private deploymentObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private deploymentSelectedUnitId?: string;
|
||||
private deploymentNotice = '추천 배치는 이미 적용되어 있습니다. 필요하면 장수를 선택해 시작 구역 안에서 위치를 바꾸세요.';
|
||||
private mapMenuObjects: Array<Phaser.GameObjects.Rectangle | Phaser.GameObjects.Text> = [];
|
||||
private mapMenuLayout?: MapMenuLayout;
|
||||
private battleSpeed: BattleSpeed = 'normal';
|
||||
@@ -3189,9 +3203,13 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.drawSidePanel();
|
||||
this.drawDebugSpritePreview();
|
||||
this.updateTurnText();
|
||||
this.time.delayedCall(180, () => this.showOpeningBattleEvent());
|
||||
if (this.shouldShowPreBattleDeployment()) {
|
||||
this.showPreBattleDeployment();
|
||||
} else {
|
||||
this.time.delayedCall(180, () => this.showOpeningBattleEvent());
|
||||
this.renderRosterPanel('ally', '행동할 장수를 선택하세요.');
|
||||
}
|
||||
this.scheduleDebugForcedBattleOutcome();
|
||||
this.renderRosterPanel('ally', '행동할 장수를 선택하세요.');
|
||||
}
|
||||
|
||||
private ensureScenarioAssets(onReady: () => void) {
|
||||
@@ -3558,6 +3576,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
hitZone.setInteractive({ useHandCursor: true });
|
||||
hitZone.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.leftButtonDown()) {
|
||||
if (this.phase === 'deployment') {
|
||||
this.suppressNextLeftClick = true;
|
||||
this.handleDeploymentUnitClick(unit);
|
||||
return;
|
||||
}
|
||||
this.selectUnit(unit);
|
||||
}
|
||||
});
|
||||
@@ -3949,6 +3972,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
});
|
||||
|
||||
battleUnits.forEach((unit) => this.positionUnitView(unit));
|
||||
this.positionDeploymentOverlayObjects();
|
||||
this.updateMiniMap();
|
||||
this.refreshFacingIndicator();
|
||||
this.updatePointerFeedback(this.input.activePointer, true);
|
||||
@@ -4075,6 +4099,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.phase === 'deployment') {
|
||||
this.updateDeploymentPointerFeedback(tile, force);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.phase === 'moving' && this.selectedUnit) {
|
||||
this.updateMovePointerFeedback(this.selectedUnit, tile, force);
|
||||
return;
|
||||
@@ -4308,6 +4337,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private selectUnit(unit: UnitData) {
|
||||
if (this.phase === 'deployment') {
|
||||
this.handleDeploymentUnitClick(unit);
|
||||
return;
|
||||
}
|
||||
|
||||
this.hideMapMenu();
|
||||
this.hideSaveSlotPanel();
|
||||
this.hideTurnEndPrompt();
|
||||
@@ -4608,6 +4642,484 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.clearPointerFeedback();
|
||||
}
|
||||
|
||||
private shouldShowPreBattleDeployment() {
|
||||
return battleScenario.id === 'first-battle-zhuo-commandery' && battleUnits.some((unit) => unit.faction === 'ally' && unit.hp > 0);
|
||||
}
|
||||
|
||||
private showPreBattleDeployment() {
|
||||
this.phase = 'deployment';
|
||||
this.activeFaction = 'ally';
|
||||
this.selectedUnit = undefined;
|
||||
this.pendingMove = undefined;
|
||||
this.targetingAction = undefined;
|
||||
this.selectedUsable = undefined;
|
||||
this.lockedTargetPreview = undefined;
|
||||
this.deploymentSelectedUnitId = battleUnits.find((unit) => unit.id === 'guan-yu' && unit.hp > 0)?.id;
|
||||
this.deploymentNotice = '추천 배치는 이미 적용되어 있습니다. 필요하면 장수를 선택해 시작 구역 안에서 위치를 바꾸세요.';
|
||||
this.clearMarkers();
|
||||
this.hideCommandMenu();
|
||||
this.hideMapMenu();
|
||||
this.hideTurnEndPrompt();
|
||||
this.hideBattleEventBanner();
|
||||
this.centerCameraOnTile(2, 16);
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
this.refreshUnitLegibilityStyles();
|
||||
this.updatePointerFeedback(this.input.activePointer, true);
|
||||
}
|
||||
|
||||
private firstBattleDeploymentSlots(): DeploymentSlot[] {
|
||||
const slots: DeploymentSlot[] = [
|
||||
{
|
||||
x: 2,
|
||||
y: 15,
|
||||
role: 'front',
|
||||
unitId: 'guan-yu',
|
||||
label: '정면 돌파',
|
||||
note: '길목 보병을 가장 먼저 받습니다.',
|
||||
icon: 'spear',
|
||||
tone: 0xd8b15f
|
||||
},
|
||||
{
|
||||
x: 1,
|
||||
y: 16,
|
||||
role: 'flank',
|
||||
unitId: 'zhang-fei',
|
||||
label: '측면 압박',
|
||||
note: '아래쪽 길로 궁병 엄호를 흔듭니다.',
|
||||
icon: 'attack',
|
||||
tone: 0xc76542
|
||||
},
|
||||
{
|
||||
x: 1,
|
||||
y: 15,
|
||||
role: 'support',
|
||||
unitId: 'liu-bei',
|
||||
label: '후방 지휘',
|
||||
note: '유비 생존과 회복 여지를 지킵니다.',
|
||||
icon: 'leadership',
|
||||
tone: 0x5f95d8
|
||||
},
|
||||
{ x: 2, y: 16, role: 'reserve', label: '예비 자리', note: '전열을 좁게 묶습니다.', icon: 'move', tone: 0x7d8a96 },
|
||||
{ x: 1, y: 17, role: 'reserve', label: '예비 자리', note: '후방을 더 안전하게 둡니다.', icon: 'move', tone: 0x7d8a96 },
|
||||
{ x: 2, y: 17, role: 'reserve', label: '예비 자리', note: '측면 출발을 늦춥니다.', icon: 'move', tone: 0x7d8a96 }
|
||||
];
|
||||
|
||||
return slots.filter((slot) => {
|
||||
if (!this.isInBounds(slot.x, slot.y)) {
|
||||
return false;
|
||||
}
|
||||
return getTerrainRule(battleMap.terrain[slot.y][slot.x]).passable !== false;
|
||||
});
|
||||
}
|
||||
|
||||
private isDeploymentTile(x: number, y: number) {
|
||||
return this.firstBattleDeploymentSlots().some((slot) => slot.x === x && slot.y === y);
|
||||
}
|
||||
|
||||
private deploymentSlotAt(x: number, y: number) {
|
||||
return this.firstBattleDeploymentSlots().find((slot) => slot.x === x && slot.y === y);
|
||||
}
|
||||
|
||||
private renderDeploymentMap() {
|
||||
this.clearDeploymentObjects();
|
||||
const selected = this.deploymentSelectedUnit();
|
||||
|
||||
this.firstBattleDeploymentSlots().forEach((slot) => {
|
||||
const occupant = this.unitAtTile(slot.x, slot.y);
|
||||
const recommendedUnit = slot.unitId ? battleUnits.find((unit) => unit.id === slot.unitId) : undefined;
|
||||
const selectedTile = selected?.x === slot.x && selected?.y === slot.y;
|
||||
const recommended = Boolean(slot.unitId);
|
||||
const fillAlpha = selectedTile ? 0.34 : recommended ? 0.2 : 0.12;
|
||||
const strokeAlpha = selectedTile ? 1 : recommended ? 0.86 : 0.46;
|
||||
const strokeWidth = selectedTile ? 3 : recommended ? 2 : 1;
|
||||
|
||||
const marker = this.trackDeploymentObject(
|
||||
this.add.rectangle(this.tileTopLeftX(slot.x), this.tileTopLeftY(slot.y), this.layout.tileSize, this.layout.tileSize, slot.tone, fillAlpha)
|
||||
);
|
||||
marker.setName(`deployment-slot-${slot.x}-${slot.y}`);
|
||||
marker.setData('tileX', slot.x);
|
||||
marker.setData('tileY', slot.y);
|
||||
marker.setOrigin(0);
|
||||
marker.setDepth(4.75);
|
||||
marker.setStrokeStyle(strokeWidth, slot.tone, strokeAlpha);
|
||||
if (this.mapMask) {
|
||||
marker.setMask(this.mapMask);
|
||||
}
|
||||
marker.setInteractive({ useHandCursor: true });
|
||||
marker.on('pointerover', () => {
|
||||
marker.setFillStyle(slot.tone, Math.min(fillAlpha + 0.14, 0.44));
|
||||
this.deploymentNotice = occupant
|
||||
? `${occupant.name}: ${slot.label}. 다른 장수를 선택한 뒤 누르면 위치를 교환합니다.`
|
||||
: `${slot.label}: ${slot.note}`;
|
||||
this.renderDeploymentPanel();
|
||||
});
|
||||
marker.on('pointerout', () => {
|
||||
marker.setFillStyle(slot.tone, fillAlpha);
|
||||
});
|
||||
marker.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.leftButtonDown()) {
|
||||
this.suppressNextLeftClick = true;
|
||||
this.handleDeploymentTileClick(slot.x, slot.y);
|
||||
}
|
||||
});
|
||||
|
||||
if (recommendedUnit) {
|
||||
const label = this.trackDeploymentObject(
|
||||
this.add.text(this.tileCenterX(slot.x), this.tileTopLeftY(slot.y) + 3, slot.label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#fff1c8',
|
||||
fontStyle: '700',
|
||||
stroke: '#05070a',
|
||||
strokeThickness: 3
|
||||
})
|
||||
);
|
||||
label.setName(`deployment-label-${slot.x}-${slot.y}`);
|
||||
label.setData('tileX', slot.x);
|
||||
label.setData('tileY', slot.y);
|
||||
label.setData('tileOffsetX', this.layout.tileSize / 2);
|
||||
label.setData('tileOffsetY', 3);
|
||||
label.setOrigin(0.5, 0);
|
||||
label.setDepth(11.2);
|
||||
if (this.mapMask) {
|
||||
label.setMask(this.mapMask);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.positionDeploymentOverlayObjects();
|
||||
}
|
||||
|
||||
private clearDeploymentObjects() {
|
||||
this.deploymentObjects.forEach((object) => object.destroy());
|
||||
this.deploymentObjects = [];
|
||||
}
|
||||
|
||||
private trackDeploymentObject<T extends Phaser.GameObjects.GameObject>(object: T) {
|
||||
this.deploymentObjects.push(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
private positionDeploymentOverlayObjects() {
|
||||
this.deploymentObjects.forEach((object) => {
|
||||
const tileX = object.getData('tileX') as number | undefined;
|
||||
const tileY = object.getData('tileY') as number | undefined;
|
||||
if (tileX === undefined || tileY === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX = (object.getData('tileOffsetX') as number | undefined) ?? 0;
|
||||
const offsetY = (object.getData('tileOffsetY') as number | undefined) ?? 0;
|
||||
const tileObject = object as Phaser.GameObjects.GameObject & {
|
||||
setPosition: (x: number, y: number) => Phaser.GameObjects.GameObject;
|
||||
setVisible: (value: boolean) => Phaser.GameObjects.GameObject;
|
||||
input?: { enabled: boolean };
|
||||
};
|
||||
const visible = this.isTileVisible(tileX, tileY);
|
||||
tileObject.setPosition(this.tileTopLeftX(tileX) + offsetX, this.tileTopLeftY(tileY) + offsetY);
|
||||
tileObject.setVisible(visible);
|
||||
if (tileObject.input) {
|
||||
tileObject.input.enabled = visible;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private renderDeploymentPanel() {
|
||||
this.clearSidePanelContent();
|
||||
this.setMiniMapVisible(false);
|
||||
|
||||
const { panelX, panelY, panelWidth, panelHeight } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const top = this.sideContentTop();
|
||||
const selected = this.deploymentSelectedUnit();
|
||||
|
||||
const header = this.trackSideObject(this.add.rectangle(left, top, width, 92, 0x101820, 0.96));
|
||||
header.setOrigin(0);
|
||||
header.setStrokeStyle(1, palette.gold, 0.78);
|
||||
this.trackSideObject(this.add.text(left + 16, top + 13, '전열 배치', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '24px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 16, top + 48, '황건 잔당의 보병 전열을 보고 시작 위치를 정합니다.', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#c8d2dd',
|
||||
wordWrap: { width: width - 32, useAdvancedWrap: true }
|
||||
}));
|
||||
|
||||
const noticeTop = top + 104;
|
||||
const notice = this.trackSideObject(this.add.rectangle(left, noticeTop, width, 54, 0x0b1118, 0.94));
|
||||
notice.setOrigin(0);
|
||||
notice.setStrokeStyle(1, selected ? palette.blue : 0x647485, selected ? 0.74 : 0.56);
|
||||
this.trackSideObject(this.add.text(left + 14, noticeTop + 10, selected ? `선택: ${selected.name}` : '장수를 선택하세요.', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '14px',
|
||||
color: selected ? '#f2e3bf' : '#d4dce6',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 14, noticeTop + 30, this.deploymentNotice, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#9fb0bf',
|
||||
fixedWidth: width - 28
|
||||
}));
|
||||
|
||||
const roleTop = noticeTop + 72;
|
||||
this.deploymentRoleSummaries().forEach((summary, index) => {
|
||||
const rowTop = roleTop + index * 72;
|
||||
const active = selected?.id === summary.unitId;
|
||||
const row = this.trackSideObject(this.add.rectangle(left, rowTop, width, 62, active ? 0x21364a : 0x101820, active ? 0.98 : 0.92));
|
||||
row.setOrigin(0);
|
||||
row.setStrokeStyle(1, active ? summary.tone : 0x53606c, active ? 0.86 : 0.52);
|
||||
row.setInteractive({ useHandCursor: true });
|
||||
row.on('pointerdown', () => {
|
||||
const unit = battleUnits.find((candidate) => candidate.id === summary.unitId && candidate.hp > 0);
|
||||
if (unit) {
|
||||
this.handleDeploymentUnitClick(unit);
|
||||
}
|
||||
});
|
||||
const icon = this.trackSideIcon(left + 31, rowTop + 31, summary.icon, 42);
|
||||
icon.setDepth(34);
|
||||
this.trackSideObject(this.add.text(left + 58, rowTop + 9, `${summary.name} · ${summary.role}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
color: active ? '#f4dfad' : '#e7edf7',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 58, rowTop + 32, summary.text, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#aeb7c2',
|
||||
fixedWidth: width - 70
|
||||
}));
|
||||
});
|
||||
|
||||
this.renderDeploymentButton(left, panelY + panelHeight - 76, width, '전투 시작', palette.gold, () => this.confirmPreBattleDeployment());
|
||||
this.renderDeploymentButton(left, panelY + panelHeight - 116, width, '추천 배치 복원', 0x647485, () => this.restoreRecommendedDeployment());
|
||||
}
|
||||
|
||||
private deploymentRoleSummaries() {
|
||||
return [
|
||||
{
|
||||
unitId: 'guan-yu',
|
||||
name: this.unitName('guan-yu'),
|
||||
role: '정면 돌파',
|
||||
text: '길목의 황건 보병을 먼저 열어 전열을 만듭니다.',
|
||||
icon: 'spear' as BattleUiIconKey,
|
||||
tone: 0xd8b15f
|
||||
},
|
||||
{
|
||||
unitId: 'zhang-fei',
|
||||
name: this.unitName('zhang-fei'),
|
||||
role: '측면 압박',
|
||||
text: '아래쪽에서 궁병 엄호와 잔당을 흔듭니다.',
|
||||
icon: 'attack' as BattleUiIconKey,
|
||||
tone: 0xc76542
|
||||
},
|
||||
{
|
||||
unitId: 'liu-bei',
|
||||
name: this.unitName('liu-bei'),
|
||||
role: '후방 지휘',
|
||||
text: '유비를 무리시키지 않고 생존과 회복 여지를 남깁니다.',
|
||||
icon: 'leadership' as BattleUiIconKey,
|
||||
tone: 0x5f95d8
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private renderDeploymentButton(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
label: string,
|
||||
tone: number,
|
||||
action: () => void
|
||||
) {
|
||||
const button = this.trackSideObject(this.add.rectangle(x, y, width, 34, 0x17232e, 0.96));
|
||||
button.setOrigin(0);
|
||||
button.setStrokeStyle(1, tone, 0.86);
|
||||
button.setInteractive({ useHandCursor: true });
|
||||
button.on('pointerover', () => button.setFillStyle(0x243746, 0.98));
|
||||
button.on('pointerout', () => button.setFillStyle(0x17232e, 0.96));
|
||||
button.on('pointerdown', () => {
|
||||
this.suppressNextLeftClick = true;
|
||||
action();
|
||||
});
|
||||
|
||||
const text = this.trackSideObject(this.add.text(x + width / 2, y + 17, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setInteractive({ useHandCursor: true });
|
||||
text.on('pointerdown', () => {
|
||||
this.suppressNextLeftClick = true;
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
private deploymentSelectedUnit() {
|
||||
return this.deploymentSelectedUnitId
|
||||
? battleUnits.find((unit) => unit.id === this.deploymentSelectedUnitId && unit.faction === 'ally' && unit.hp > 0)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private handleDeploymentUnitClick(unit: UnitData) {
|
||||
if (this.phase !== 'deployment') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (unit.faction !== 'ally' || unit.hp <= 0) {
|
||||
this.deploymentNotice = '전열 배치 중에는 아군 시작 위치만 조정할 수 있습니다.';
|
||||
this.renderDeploymentPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = this.deploymentSelectedUnit();
|
||||
if (!selected || selected.id === unit.id) {
|
||||
this.deploymentSelectedUnitId = unit.id;
|
||||
this.deploymentNotice = `${unit.name}을 선택했습니다. 빈 시작 칸을 누르면 이동하고, 다른 장수를 누르면 위치를 바꿉니다.`;
|
||||
soundDirector.playSelect();
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.swapDeploymentUnits(selected, unit);
|
||||
}
|
||||
|
||||
private handleDeploymentTileClick(x: number, y: number) {
|
||||
if (this.phase !== 'deployment') {
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = this.deploymentSelectedUnit();
|
||||
const occupant = this.unitAtTile(x, y);
|
||||
if (occupant?.faction === 'ally') {
|
||||
this.handleDeploymentUnitClick(occupant);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
this.deploymentNotice = '먼저 유비, 관우, 장비 중 한 명을 선택하세요.';
|
||||
this.renderDeploymentPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isDeploymentTile(x, y)) {
|
||||
this.deploymentNotice = '첫 전투에서는 표시된 시작 구역 안에서만 전열을 바꿀 수 있습니다.';
|
||||
this.renderDeploymentPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (occupant) {
|
||||
this.deploymentNotice = `${occupant.name}이 있는 칸입니다. 시작 구역 안의 빈 칸을 선택하세요.`;
|
||||
this.renderDeploymentPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
const fromX = selected.x;
|
||||
const fromY = selected.y;
|
||||
selected.x = x;
|
||||
selected.y = y;
|
||||
this.moveUnitView(selected, x, y, this.unitViews.get(selected.id), 150, fromX, fromY);
|
||||
this.deploymentNotice = `${selected.name}을 ${this.deploymentSlotAt(x, y)?.label ?? '시작 위치'}에 배치했습니다.`;
|
||||
this.updateMiniMap();
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
soundDirector.playSelect();
|
||||
}
|
||||
|
||||
private swapDeploymentUnits(selected: UnitData, target: UnitData) {
|
||||
const selectedFrom = { x: selected.x, y: selected.y };
|
||||
const targetFrom = { x: target.x, y: target.y };
|
||||
selected.x = targetFrom.x;
|
||||
selected.y = targetFrom.y;
|
||||
target.x = selectedFrom.x;
|
||||
target.y = selectedFrom.y;
|
||||
this.moveUnitView(selected, selected.x, selected.y, this.unitViews.get(selected.id), 150, selectedFrom.x, selectedFrom.y);
|
||||
this.moveUnitView(target, target.x, target.y, this.unitViews.get(target.id), 150, targetFrom.x, targetFrom.y);
|
||||
this.deploymentNotice = `${selected.name}과 ${target.name}의 위치를 바꿨습니다.`;
|
||||
this.updateMiniMap();
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
soundDirector.playSelect();
|
||||
}
|
||||
|
||||
private restoreRecommendedDeployment() {
|
||||
const recommended = this.firstBattleDeploymentSlots().filter((slot) => slot.unitId);
|
||||
recommended.forEach((slot) => {
|
||||
const unit = battleUnits.find((candidate) => candidate.id === slot.unitId && candidate.hp > 0);
|
||||
if (!unit) {
|
||||
return;
|
||||
}
|
||||
const fromX = unit.x;
|
||||
const fromY = unit.y;
|
||||
unit.x = slot.x;
|
||||
unit.y = slot.y;
|
||||
this.moveUnitView(unit, slot.x, slot.y, this.unitViews.get(unit.id), 130, fromX, fromY);
|
||||
});
|
||||
this.deploymentSelectedUnitId = 'guan-yu';
|
||||
this.deploymentNotice = '추천 배치로 되돌렸습니다. 관우는 정면, 장비는 측면, 유비는 후방에 섭니다.';
|
||||
this.updateMiniMap();
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
soundDirector.playSelect();
|
||||
}
|
||||
|
||||
private confirmPreBattleDeployment() {
|
||||
if (this.phase !== 'deployment') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearDeploymentObjects();
|
||||
this.clearPointerFeedback();
|
||||
this.deploymentSelectedUnitId = undefined;
|
||||
this.selectedUnit = undefined;
|
||||
this.pendingMove = undefined;
|
||||
this.phase = 'idle';
|
||||
this.refreshUnitLegibilityStyles();
|
||||
this.renderRosterPanel('ally', '전열 배치가 끝났습니다. 관우로 길목을 열고, 장비로 측면을 압박하세요.');
|
||||
this.showOpeningBattleEvent();
|
||||
soundDirector.playSelect();
|
||||
}
|
||||
|
||||
private updateDeploymentPointerFeedback(tile: { x: number; y: number }, force: boolean) {
|
||||
const selected = this.deploymentSelectedUnit();
|
||||
const occupant = this.unitAtTile(tile.x, tile.y);
|
||||
const slot = this.deploymentSlotAt(tile.x, tile.y);
|
||||
const key = `deployment:${tile.x},${tile.y}:${selected?.id ?? 'none'}:${occupant?.id ?? 'empty'}:${slot?.label ?? 'out'}`;
|
||||
if (!force && this.lastPointerFeedbackKey === key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (occupant?.faction === 'ally') {
|
||||
const text = selected && selected.id !== occupant.id
|
||||
? `${occupant.name}: 클릭하면 ${selected.name}과 위치 교환`
|
||||
: `${occupant.name}: 선택 후 시작 구역 칸으로 이동`;
|
||||
this.showPointerFeedback(tile, palette.blue, text, key, 0x1c7ed6, 0.12, 0.9);
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot && selected) {
|
||||
this.showPointerFeedback(tile, slot.tone, `${slot.label}: ${selected.name} 배치 가능`, key, slot.tone, 0.16, 0.92);
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
this.showPointerFeedback(tile, slot.tone, `${slot.label}: 먼저 장수를 선택하세요`, key, slot.tone, 0.12, 0.7);
|
||||
return;
|
||||
}
|
||||
|
||||
this.showPointerFeedback(tile, 0x9b4d45, '표시된 시작 구역 안에서만 배치할 수 있습니다.', key, 0x9b4d45, 0.08, 0.66);
|
||||
}
|
||||
|
||||
private showEnemyThreatRange() {
|
||||
this.clearMarkers();
|
||||
const threatTiles = this.enemyThreatTiles();
|
||||
@@ -9517,6 +10029,15 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.phase === 'deployment') {
|
||||
this.deploymentSelectedUnitId = undefined;
|
||||
this.deploymentNotice = '선택을 해제했습니다. 배치할 장수를 다시 선택하세요.';
|
||||
this.renderDeploymentMap();
|
||||
this.renderDeploymentPanel();
|
||||
this.clearPointerFeedback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cancelAttackTargeting()) {
|
||||
return;
|
||||
}
|
||||
@@ -9543,6 +10064,14 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.phase === 'deployment') {
|
||||
const tile = this.pointerToTile(pointer);
|
||||
if (tile) {
|
||||
this.handleDeploymentTileClick(tile.x, tile.y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const mapMenuAction = this.mapMenuActionAt(pointer.x, pointer.y);
|
||||
if (mapMenuAction) {
|
||||
this.runMapMenuAction(mapMenuAction);
|
||||
@@ -9623,7 +10152,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showMapMenu(pointerX: number, pointerY: number) {
|
||||
if (this.phase === 'command' || this.phase === 'targeting' || this.phase === 'animating') {
|
||||
if (this.phase === 'deployment' || this.phase === 'command' || this.phase === 'targeting' || this.phase === 'animating') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user