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', classKey: 'yellowTurban',
level: 1, level: 1,
exp: 6, exp: 6,
hp: 20, hp: 19,
maxHp: 20, maxHp: 19,
attack: 6, attack: 6,
move: 3, move: 3,
stats: { might: 49, intelligence: 34, leadership: 38, agility: 45, luck: 40 }, 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 }, armor: { itemId: 'rebel-vest', level: 1, exp: 4 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
}, },
x: 10, x: 6,
y: 12 y: 13
}, },
{ {
id: 'rebel-b', id: 'rebel-b',
@@ -313,7 +313,7 @@ export const firstBattleUnits: UnitData[] = [
armor: { itemId: 'rebel-vest', level: 1, exp: 5 }, armor: { itemId: 'rebel-vest', level: 1, exp: 5 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
}, },
x: 12, x: 8,
y: 12 y: 12
}, },
{ {
@@ -334,7 +334,7 @@ export const firstBattleUnits: UnitData[] = [
armor: { itemId: 'cloth-armor', level: 1, exp: 6 }, armor: { itemId: 'cloth-armor', level: 1, exp: 6 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 } accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
}, },
x: 13, x: 10,
y: 11 y: 11
}, },
{ {
@@ -345,8 +345,8 @@ export const firstBattleUnits: UnitData[] = [
classKey: 'yellowTurban', classKey: 'yellowTurban',
level: 1, level: 1,
exp: 8, exp: 8,
hp: 21, hp: 20,
maxHp: 21, maxHp: 20,
attack: 6, attack: 6,
move: 3, move: 3,
stats: { might: 51, intelligence: 31, leadership: 39, agility: 44, luck: 39 }, 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 }, armor: { itemId: 'rebel-vest', level: 1, exp: 5 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
}, },
x: 6, x: 7,
y: 8 y: 9
}, },
{ {
id: 'rebel-e', id: 'rebel-e',
@@ -376,7 +376,7 @@ export const firstBattleUnits: UnitData[] = [
armor: { itemId: 'rebel-vest', level: 1, exp: 5 }, armor: { itemId: 'rebel-vest', level: 1, exp: 5 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 } accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
}, },
x: 5, x: 10,
y: 9 y: 9
}, },
{ {
@@ -387,8 +387,8 @@ export const firstBattleUnits: UnitData[] = [
classKey: 'cavalry', classKey: 'cavalry',
level: 2, level: 2,
exp: 14, exp: 14,
hp: 24, hp: 23,
maxHp: 24, maxHp: 23,
attack: 8, attack: 8,
move: 5, move: 5,
stats: { might: 58, intelligence: 34, leadership: 45, agility: 62, luck: 42 }, 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 }, armor: { itemId: 'rebel-vest', level: 1, exp: 6 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
}, },
x: 9, x: 12,
y: 9 y: 10
}, },
{ {
id: 'rebel-archer-b', id: 'rebel-archer-b',
@@ -429,8 +429,8 @@ export const firstBattleUnits: UnitData[] = [
classKey: 'cavalry', classKey: 'cavalry',
level: 2, level: 2,
exp: 16, exp: 16,
hp: 25, hp: 24,
maxHp: 25, maxHp: 24,
attack: 8, attack: 8,
move: 5, move: 5,
stats: { might: 60, intelligence: 35, leadership: 48, agility: 63, luck: 45 }, 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 }, armor: { itemId: 'rebel-vest', level: 1, exp: 7 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 } accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
}, },
x: 14, x: 13,
y: 7 y: 7
}, },
{ {
@@ -450,8 +450,8 @@ export const firstBattleUnits: UnitData[] = [
classKey: 'yellowTurban', classKey: 'yellowTurban',
level: 2, level: 2,
exp: 10, exp: 10,
hp: 23, hp: 22,
maxHp: 23, maxHp: 22,
attack: 7, attack: 7,
move: 3, move: 3,
stats: { might: 54, intelligence: 35, leadership: 43, agility: 47, luck: 42 }, stats: { might: 54, intelligence: 35, leadership: 43, agility: 47, luck: 42 },
@@ -492,9 +492,9 @@ export const firstBattleUnits: UnitData[] = [
classKey: 'rebelLeader', classKey: 'rebelLeader',
level: 2, level: 2,
exp: 18, exp: 18,
hp: 30, hp: 32,
maxHp: 30, maxHp: 32,
attack: 9, attack: 10,
move: 3, move: 3,
stats: { might: 64, intelligence: 46, leadership: 61, agility: 52, luck: 50 }, stats: { might: 64, intelligence: 46, leadership: 61, agility: 52, luck: 50 },
equipment: { equipment: {

View File

@@ -2074,8 +2074,11 @@ export class BattleScene extends Phaser.Scene {
} }
if (objective.kind === 'secure-terrain') { if (objective.kind === 'secure-terrain') {
const terrain = objective.terrain ?? 'village'; const terrain = (objective.terrain ?? 'village') as TerrainType;
const secured = !this.hasEnemyOnTerrain(terrain); const secured =
terrain === 'village'
? !this.hasEnemyThreateningTerrain(terrain, 2)
: !this.hasEnemyOnTerrain(terrain);
const achieved = outcome ? outcome === 'victory' && secured : secured; const achieved = outcome ? outcome === 'victory' && secured : secured;
return { return {
id: objective.id, id: objective.id,
@@ -2901,25 +2904,98 @@ export class BattleScene extends Phaser.Scene {
this.triggerBattleEvent('leader-wavering', '두령 동요', [`${leader.name}의 기세가 꺾였습니다.`, '두령을 몰아붙이면 황건적의 전열이 무너집니다.']); 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', '마을 확보', ['마을 주변의 황건적을 몰아냈습니다.', '승리 시 마을 확보 보상이 추가됩니다.']); 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') { if (this.turnNumber === 2 && this.activeFaction === 'ally') {
this.triggerBattleEvent('enemy-posture', '적 전술 변화', ['기병은 계속 접근하고, 궁병은 자리를 지키며 사격합니다.', '숲과 마을 지형을 이용해 피해를 줄이세요.']); this.triggerBattleEvent('enemy-posture', '적 전술 변화', ['기병은 계속 접근하고, 궁병은 자리를 지키며 사격합니다.', '숲과 마을 지형을 이용해 피해를 줄이세요.']);
} }
} }
private hasEnemyOnVillageTile() { private hasEnemyThreateningVillage() {
return this.hasEnemyOnTerrain('village'); 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 battleUnits.some((unit) => {
return unit.faction === 'enemy' && unit.hp > 0 && battleMap.terrain[unit.y][unit.x] === terrain; 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) { private isUnitAlive(unitId: string) {
const unit = battleUnits.find((candidate) => candidate.id === unitId); const unit = battleUnits.find((candidate) => candidate.id === unitId);
return Boolean(unit && unit.hp > 0); return Boolean(unit && unit.hp > 0);