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

@@ -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: {

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