diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index df99cfa..49aaac6 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -105,6 +105,76 @@ try { throw new Error('Expected all unit action textures to be loaded.'); } + const movementBlockingState = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene'); + const state = window.__HEROS_DEBUG__?.battle(); + const liuBei = state?.units?.find((unit) => unit.id === 'liu-bei'); + const guanYu = state?.units?.find((unit) => unit.id === 'guan-yu'); + const enemy = state?.units?.find((unit) => unit.faction === 'enemy'); + + if (!scene || !liuBei || !guanYu || !enemy) { + throw new Error('Expected battle scene and movement test units.'); + } + + return { + allyBlocksPath: scene.isMovementBlocked(liuBei, guanYu.x, guanYu.y), + enemyBlocksPath: scene.isMovementBlocked(liuBei, enemy.x, enemy.y), + allyStopTileReachable: scene.reachableTiles(liuBei).some((tile) => tile.x === guanYu.x && tile.y === guanYu.y) + }; + }); + if ( + movementBlockingState.allyBlocksPath || + !movementBlockingState.enemyBlocksPath || + movementBlockingState.allyStopTileReachable + ) { + throw new Error(`Expected allies to be passable path blockers only as stop tiles, and enemies to block movement: ${JSON.stringify(movementBlockingState)}`); + } + + const liuBeiScreen = await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene'); + const state = window.__HEROS_DEBUG__?.battle(); + const liuBei = state?.units?.find((unit) => unit.id === 'liu-bei'); + + if (!scene || !liuBei) { + throw new Error('Expected Liu Bei and battle scene for stand-still command test.'); + } + + return { + x: scene.tileCenterX(liuBei.x), + y: scene.tileCenterY(liuBei.y) + }; + }); + await page.mouse.click(liuBeiScreen.x, liuBeiScreen.y); + await page.waitForTimeout(140); + await page.mouse.click(liuBeiScreen.x, liuBeiScreen.y); + await page.waitForTimeout(180); + + const standStillCommandState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle()); + if ( + standStillCommandState?.phase !== 'command' || + standStillCommandState.selectedUnitId !== 'liu-bei' || + standStillCommandState.pendingMove?.unitId !== 'liu-bei' || + standStillCommandState.pendingMove.from.x !== standStillCommandState.pendingMove.to.x || + standStillCommandState.pendingMove.from.y !== standStillCommandState.pendingMove.to.y + ) { + throw new Error(`Expected Liu Bei to open commands without moving: ${JSON.stringify(standStillCommandState)}`); + } + + await page.evaluate(() => { + const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene'); + if (!scene) { + return; + } + scene.selectedUnit = undefined; + scene.pendingMove = undefined; + scene.targetingAction = undefined; + scene.selectedUsable = undefined; + scene.phase = 'idle'; + scene.clearMarkers(); + scene.hideCommandMenu(); + scene.hideMapMenu(); + }); + await page.waitForTimeout(700); await page.mouse.click(260, 300, { button: 'right' }); await page.waitForTimeout(120); diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 7c883b8..4c42961 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -1106,6 +1106,11 @@ export class BattleScene extends Phaser.Scene { this.centerCameraOnTile(unit.x, unit.y); } + if (this.phase === 'moving' && this.selectedUnit?.id === unit.id) { + this.moveSelectedUnit(unit.x, unit.y); + return; + } + if (this.phase === 'targeting' && this.selectedUnit) { if (unit.hp > 0 && this.targetingAction) { if (this.selectedUsable && this.selectedUsable.effect !== 'damage') { @@ -1214,6 +1219,7 @@ export class BattleScene extends Phaser.Scene { const fromX = unit.x; const fromY = unit.y; + const stayingInPlace = fromX === x && fromY === y; unit.x = x; unit.y = y; this.pendingMove = { unit, fromX, fromY, toX: x, toY: y }; @@ -1224,7 +1230,9 @@ export class BattleScene extends Phaser.Scene { const targetX = this.tileCenterX(x); const targetY = this.tileCenterY(y); - this.moveUnitView(unit, x, y, view); + if (!stayingInPlace) { + this.moveUnitView(unit, x, y, view); + } this.showCommandMenu(unit, pointer?.x ?? targetX, pointer?.y ?? targetY); this.renderUnitDetail(unit, '공격, 책략, 도구, 대기 중 하나를 선택하세요.\n우클릭하면 이동을 취소합니다.'); } @@ -1240,9 +1248,10 @@ export class BattleScene extends Phaser.Scene { { x: 0, y: 1 }, { x: 0, y: -1 } ]; - const costs = new Map([[this.tileKey(unit.x, unit.y), 0]]); + const originKey = this.tileKey(unit.x, unit.y); + const costs = new Map([[originKey, 0]]); const queue = [{ x: unit.x, y: unit.y, cost: 0 }]; - const reachable = new Map(); + const reachable = new Map([[originKey, { x: unit.x, y: unit.y, cost: 0 }]]); while (queue.length > 0) { queue.sort((a, b) => a.cost - b.cost); @@ -1254,7 +1263,7 @@ export class BattleScene extends Phaser.Scene { directions.forEach((direction) => { const nextX = current.x + direction.x; const nextY = current.y + direction.y; - if (!this.isInBounds(nextX, nextY) || this.isOccupied(nextX, nextY, unit.id)) { + if (!this.isInBounds(nextX, nextY) || this.isMovementBlocked(unit, nextX, nextY)) { return; } @@ -1271,7 +1280,9 @@ export class BattleScene extends Phaser.Scene { costs.set(key, nextCost); const tile = { x: nextX, y: nextY, cost: nextCost }; - reachable.set(key, tile); + if (!this.isOccupied(nextX, nextY, unit.id)) { + reachable.set(key, tile); + } queue.push(tile); }); } @@ -1300,6 +1311,15 @@ export class BattleScene extends Phaser.Scene { return battleUnits.some((unit) => unit.id !== exceptUnitId && unit.hp > 0 && unit.x === x && unit.y === y); } + private isMovementBlocked(unit: UnitData, x: number, y: number) { + return battleUnits.some((other) => { + if (other.id === unit.id || other.hp <= 0 || other.x !== x || other.y !== y) { + return false; + } + return other.faction !== unit.faction; + }); + } + private tileCenterX(x: number) { return this.tileTopLeftX(x) + this.layout.tileSize / 2; }