diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 83a565a..52db0a7 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -282,8 +282,8 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'yellowTurban', level: 1, exp: 6, - hp: 20, - maxHp: 20, + hp: 19, + maxHp: 19, attack: 6, move: 3, stats: { might: 49, intelligence: 34, leadership: 38, agility: 45, luck: 40 }, @@ -292,8 +292,8 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 4 }, accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } }, - x: 10, - y: 12 + x: 6, + y: 13 }, { id: 'rebel-b', @@ -313,7 +313,7 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 5 }, accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } }, - x: 12, + x: 8, y: 12 }, { @@ -334,7 +334,7 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'cloth-armor', level: 1, exp: 6 }, accessory: { itemId: 'wind-quiver', level: 1, exp: 0 } }, - x: 13, + x: 10, y: 11 }, { @@ -345,8 +345,8 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'yellowTurban', level: 1, exp: 8, - hp: 21, - maxHp: 21, + hp: 20, + maxHp: 20, attack: 6, move: 3, stats: { might: 51, intelligence: 31, leadership: 39, agility: 44, luck: 39 }, @@ -355,8 +355,8 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 5 }, accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } }, - x: 6, - y: 8 + x: 7, + y: 9 }, { id: 'rebel-e', @@ -376,7 +376,7 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 5 }, accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } }, - x: 5, + x: 10, y: 9 }, { @@ -387,8 +387,8 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'cavalry', level: 2, exp: 14, - hp: 24, - maxHp: 24, + hp: 23, + maxHp: 23, attack: 8, move: 5, stats: { might: 58, intelligence: 34, leadership: 45, agility: 62, luck: 42 }, @@ -397,8 +397,8 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 6 }, accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } }, - x: 9, - y: 9 + x: 12, + y: 10 }, { id: 'rebel-archer-b', @@ -429,8 +429,8 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'cavalry', level: 2, exp: 16, - hp: 25, - maxHp: 25, + hp: 24, + maxHp: 24, attack: 8, move: 5, stats: { might: 60, intelligence: 35, leadership: 48, agility: 63, luck: 45 }, @@ -439,7 +439,7 @@ export const firstBattleUnits: UnitData[] = [ armor: { itemId: 'rebel-vest', level: 1, exp: 7 }, accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } }, - x: 14, + x: 13, y: 7 }, { @@ -450,8 +450,8 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'yellowTurban', level: 2, exp: 10, - hp: 23, - maxHp: 23, + hp: 22, + maxHp: 22, attack: 7, move: 3, stats: { might: 54, intelligence: 35, leadership: 43, agility: 47, luck: 42 }, @@ -492,9 +492,9 @@ export const firstBattleUnits: UnitData[] = [ classKey: 'rebelLeader', level: 2, exp: 18, - hp: 30, - maxHp: 30, - attack: 9, + hp: 32, + maxHp: 32, + attack: 10, move: 3, stats: { might: 64, intelligence: 46, leadership: 61, agility: 52, luck: 50 }, equipment: { diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index a215718..308781e 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -2074,8 +2074,11 @@ export class BattleScene extends Phaser.Scene { } if (objective.kind === 'secure-terrain') { - const terrain = objective.terrain ?? 'village'; - const secured = !this.hasEnemyOnTerrain(terrain); + const terrain = (objective.terrain ?? 'village') as TerrainType; + const secured = + terrain === 'village' + ? !this.hasEnemyThreateningTerrain(terrain, 2) + : !this.hasEnemyOnTerrain(terrain); const achieved = outcome ? outcome === 'victory' && secured : secured; return { id: objective.id, @@ -2901,25 +2904,98 @@ export class BattleScene extends Phaser.Scene { this.triggerBattleEvent('leader-wavering', '두령 동요', [`${leader.name}의 기세가 꺾였습니다.`, '두령을 몰아붙이면 황건적의 전열이 무너집니다.']); } - if (!this.hasEnemyOnVillageTile()) { + const endangeredAlly = this.endangeredAlly(); + if (endangeredAlly) { + this.triggerBattleEvent('ally-danger', '아군 위기', [ + `${endangeredAlly.name}의 병력이 크게 줄었습니다.`, + '유비가 퇴각하면 패배합니다. 마을과 성채 지형을 이용해 전열을 다시 잡으세요.' + ]); + } + + if (this.isAnyAllyNearTerrain('village', 2)) { + this.triggerBattleEvent('village-approach', '마을 어귀', [ + '마을 주변에 황건적이 몰려 있습니다.', + '마을의 적을 몰아내면 승리 보상이 추가됩니다.' + ]); + } + + if (!this.hasEnemyThreateningVillage()) { this.triggerBattleEvent('village-secured', '마을 확보', ['마을 주변의 황건적을 몰아냈습니다.', '승리 시 마을 확보 보상이 추가됩니다.']); } + if (this.areUnitsDefeated(['rebel-a', 'rebel-b', 'rebel-c'])) { + this.triggerBattleEvent('vanguard-broken', '선봉 격파', [ + '남쪽 길목의 황건 선봉이 무너졌습니다.', + '이제 중앙 마을과 기병의 움직임을 경계하세요.' + ]); + } + + if (this.isEnemyClassNearAlly('cavalry', 6)) { + this.triggerBattleEvent('cavalry-approach', '기병 접근', [ + '황건 기병이 빠르게 압박해옵니다.', + '위험 범위를 켜고 숲이나 마을 쪽으로 전선을 잡으세요.' + ]); + } + if (this.turnNumber === 2 && this.activeFaction === 'ally') { this.triggerBattleEvent('enemy-posture', '적 전술 변화', ['기병은 계속 접근하고, 궁병은 자리를 지키며 사격합니다.', '숲과 마을 지형을 이용해 피해를 줄이세요.']); } } - private hasEnemyOnVillageTile() { - return this.hasEnemyOnTerrain('village'); + private hasEnemyThreateningVillage() { + return this.hasEnemyThreateningTerrain('village', 2); } - private hasEnemyOnTerrain(terrain: string) { + private hasEnemyThreateningTerrain(terrain: TerrainType, distance: number) { + return battleUnits.some((unit) => { + return unit.faction === 'enemy' && unit.hp > 0 && this.isUnitNearTerrain(unit, terrain, distance); + }); + } + + private hasEnemyOnTerrain(terrain: TerrainType) { return battleUnits.some((unit) => { return unit.faction === 'enemy' && unit.hp > 0 && battleMap.terrain[unit.y][unit.x] === terrain; }); } + private isAnyAllyNearTerrain(terrain: TerrainType, distance: number) { + return battleUnits.some((unit) => { + return unit.faction === 'ally' && unit.hp > 0 && this.isUnitNearTerrain(unit, terrain, distance); + }); + } + + private isUnitNearTerrain(unit: UnitData, terrain: TerrainType, distance: number) { + return battleMap.terrain.some((row, y) => { + return row.some((tile, x) => tile === terrain && this.tileDistanceTo(unit.x, unit.y, x, y) <= distance); + }); + } + + private areUnitsDefeated(unitIds: string[]) { + return unitIds.every((unitId) => this.isUnitDefeated(unitId)); + } + + private isEnemyClassNearAlly(classKey: UnitClassKey, distance: number) { + return battleUnits.some((enemy) => { + if (enemy.faction !== 'enemy' || enemy.hp <= 0 || enemy.classKey !== classKey) { + return false; + } + + return battleUnits.some((ally) => { + return ally.faction === 'ally' && ally.hp > 0 && this.tileDistance(enemy, ally) <= distance; + }); + }); + } + + private endangeredAlly() { + return battleUnits.find((unit) => { + return unit.faction === 'ally' && unit.hp > 0 && unit.hp <= Math.ceil(unit.maxHp * 0.35); + }); + } + + private tileDistanceTo(x1: number, y1: number, x2: number, y2: number) { + return Math.abs(x1 - x2) + Math.abs(y1 - y2); + } + private isUnitAlive(unitId: string) { const unit = battleUnits.find((candidate) => candidate.id === unitId); return Boolean(unit && unit.hp > 0);