Tune first battle pacing and events

This commit is contained in:
2026-06-22 19:42:15 +09:00
parent 0b7047e8bf
commit b2a4a576c5
2 changed files with 105 additions and 29 deletions

View File

@@ -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);