Add Yuan Shao refuge road chapter

This commit is contained in:
2026-06-23 04:23:14 +09:00
parent 09c1390ba5
commit 821cebde0e
11 changed files with 873 additions and 18 deletions

View File

@@ -1,5 +1,9 @@
import {
firstBattleBonds,
fifteenthBattleBonds,
fifteenthBattleMap,
fifteenthBattleUnits,
fifteenthBattleVictoryPages,
fourteenthBattleBonds,
fourteenthBattleMap,
fourteenthBattleUnits,
@@ -75,7 +79,8 @@ export type BattleScenarioId =
| 'eleventh-battle-xudu-refuge-road'
| 'twelfth-battle-xiapi-outer-siege'
| 'thirteenth-battle-xiapi-final'
| 'fourteenth-battle-cao-break';
| 'fourteenth-battle-cao-break'
| 'fifteenth-battle-yuan-refuge-road';
export type BattleObjectiveKind = 'defeat-leader' | 'keep-unit-alive' | 'secure-terrain' | 'quick-victory';
@@ -856,6 +861,59 @@ export const fourteenthBattleScenario: BattleScenarioDefinition = {
nextCampScene: 'CampScene'
};
export const fifteenthBattleScenario: BattleScenarioDefinition = {
id: 'fifteenth-battle-yuan-refuge-road',
title: '원소 의탁로',
victoryConditionLabel: '채양 격파',
defeatConditionLabel: '유비 퇴각',
openingObjectiveLines: [
'유비군은 조조의 추격을 피해 원소 진영으로 향합니다. 채양을 격파하면 북상로의 압박을 끊고 접선지로 나아갈 수 있습니다.',
'강줄기와 고갯길 사이로 추격 기병이 파고들고, 궁병은 접선지를 향한 길을 끊으려 합니다. 전열을 세우면서도 손건과 간옹의 책략 지원을 살려야 합니다.',
'원소 사자가 기다리는 북동쪽 접선지를 확보하고 24턴 안에 승리하면 다음 의탁 장을 더 유리하게 시작합니다.'
],
map: fifteenthBattleMap,
units: fifteenthBattleUnits,
bonds: fifteenthBattleBonds,
mapTextureKey: 'battle-map-fifteenth',
leaderUnitId: 'yuan-refuge-leader-cai-yang',
quickVictoryTurnLimit: 24,
baseVictoryGold: 1760,
objectives: [
{
id: 'leader',
kind: 'defeat-leader',
label: '채양 격파',
rewardGold: 1120,
unitId: 'yuan-refuge-leader-cai-yang'
},
{
id: 'liu-bei',
kind: 'keep-unit-alive',
label: '유비 생존',
rewardGold: 400,
unitId: 'liu-bei'
},
{
id: 'rendezvous',
kind: 'secure-terrain',
label: '원소 사자 접선지 확보',
rewardGold: 640,
terrain: 'fort'
},
{
id: 'quick',
kind: 'quick-victory',
label: '24턴 이내 승리',
rewardGold: 500,
maxTurn: 24
}
],
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
itemRewards: ['콩 6', '상처약 3', '탁주 2', '원소 접선 문서 +1'],
victoryPages: fifteenthBattleVictoryPages,
nextCampScene: 'CampScene'
};
export const defaultBattleScenarioId: BattleScenarioId = firstBattleScenario.id;
export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition> = {
@@ -872,7 +930,8 @@ export const battleScenarios: Record<BattleScenarioId, BattleScenarioDefinition>
'eleventh-battle-xudu-refuge-road': eleventhBattleScenario,
'twelfth-battle-xiapi-outer-siege': twelfthBattleScenario,
'thirteenth-battle-xiapi-final': thirteenthBattleScenario,
'fourteenth-battle-cao-break': fourteenthBattleScenario
'fourteenth-battle-cao-break': fourteenthBattleScenario,
'fifteenth-battle-yuan-refuge-road': fifteenthBattleScenario
};
export const defaultBattleScenario = battleScenarios[defaultBattleScenarioId];

View File

@@ -4,6 +4,7 @@ import {
eighthBattleScenario,
eleventhBattleScenario,
fifthBattleScenario,
fifteenthBattleScenario,
fourteenthBattleScenario,
fourthBattleScenario,
ninthBattleScenario,
@@ -23,6 +24,8 @@ import {
eleventhBattleVictoryPages,
fifthBattleIntroPages,
fifthBattleVictoryPages,
fifteenthBattleIntroPages,
fifteenthBattleVictoryPages,
fourteenthBattleIntroPages,
fourteenthBattleVictoryPages,
firstBattleVictoryPages,
@@ -193,13 +196,24 @@ const sortieFlows: Record<string, SortieFlow> = {
},
[fourteenthBattleScenario.id]: {
afterBattleId: fourteenthBattleScenario.id,
eyebrow: '다음 장 준비',
title: '원소 의탁의 길',
eyebrow: '다음 장',
title: fifteenthBattleScenario.title,
description:
'조조의 장막을 벗어난 유비군은 원소 진영으로 향할 수밖에 없습니다. 다음 흐름은 원소 의탁과 조조의 추격을 피해 북상하는 장으로 이어집니다.',
rewardHint: '다음 장: 원소 의탁 준비 중',
pages: fourteenthBattleVictoryPages,
unavailableNotice: '원소 의탁 장은 다음 작업에서 이어집니다. 군영에서 성장, 대화, 출전 구성을 정비하십시오.'
'조조의 장막을 벗어난 유비군은 원소 진영으로 향해야 합니다. 그러나 북상로에는 채양의 추격대가 깔려 있어, 접선지까지 길을 열어야 합니다.',
rewardHint: `예상 보상: ${fifteenthBattleScenario.title} 개방 / 원소 접선`,
nextBattleId: fifteenthBattleScenario.id,
campaignStep: 'fifteenth-battle',
pages: [...fourteenthBattleVictoryPages, ...fifteenthBattleIntroPages]
},
[fifteenthBattleScenario.id]: {
afterBattleId: fifteenthBattleScenario.id,
eyebrow: '다음 장 준비',
title: '유표 의탁의 길',
description:
'원소 진영에 몸을 의탁했지만, 큰 세력의 틈은 오래 머물 곳이 되기 어렵습니다. 다음 흐름은 원소 진영을 떠나 유표에게 의탁하는 장으로 이어집니다.',
rewardHint: '다음 장: 유표 의탁 준비 중',
pages: fifteenthBattleVictoryPages,
unavailableNotice: '유표 의탁 장은 다음 작업에서 이어집니다. 군영에서 성장, 대화, 출전 구성을 정비하십시오.'
}
};

View File

@@ -941,6 +941,71 @@ export const fourteenthBattleVictoryPages: StoryPage[] = [
}
];
export const fifteenthBattleIntroPages: StoryPage[] = [
{
id: 'fifteenth-yuan-envoy-rumor',
bgm: 'story-dark',
chapter: '북쪽의 초대',
background: 'story-militia',
speaker: '손건',
text: '원소 진영에서 사자가 내려왔습니다. 조조를 떠난 주공을 받아들일 뜻은 있으나, 북상로 곳곳에는 조조군 추격대가 깔려 있습니다.'
},
{
id: 'fifteenth-liu-bei-under-yuan-shadow',
bgm: 'battle-prep',
chapter: '원소 의탁',
background: 'story-liu-bei',
speaker: '유비',
portrait: 'liuBei',
text: '원소의 깃발 아래로 들어가는 일은 뜻을 접는 일이 아니다. 지금은 큰 세력의 틈에서 백성을 지킬 시간을 얻어야 한다.'
},
{
id: 'fifteenth-guan-yu-pursuit-road',
bgm: 'battle-prep',
chapter: '추격로',
background: 'story-three-heroes',
speaker: '관우',
portrait: 'guanYu',
text: '추격대가 강가와 고갯길을 나누어 막고 있습니다. 채양의 군세를 물리치고 접선지를 확보해야 원소 진영과 이어질 수 있습니다.'
},
{
id: 'fifteenth-yuan-road-sortie',
bgm: 'battle-prep',
chapter: '북상 전투',
background: 'story-sortie',
speaker: '장비',
portrait: 'zhangFei',
text: '쫓아오는 놈들이 조조의 명이라 떠들어도 결국 길을 막는 적일 뿐입니다. 형님, 이번엔 북쪽 길을 시원하게 열어 봅시다!'
}
];
export const fifteenthBattleVictoryPages: StoryPage[] = [
{
id: 'fifteenth-victory-pursuit-breaks',
bgm: 'militia-theme',
chapter: '추격을 끊다',
background: 'story-sortie',
text: '채양의 추격대가 흩어지자 북상로의 먼지가 잠시 가라앉았다. 유비군은 원소의 사자가 기다리는 접선지에 닿을 수 있었다.'
},
{
id: 'fifteenth-yuan-camp-opens',
bgm: 'story-dark',
chapter: '원소의 장막',
background: 'story-militia',
speaker: '간옹',
text: '원소의 진영은 넓고 군세는 크지만, 그만큼 말과 눈도 많습니다. 몸을 맡기되 마음까지 맡기지는 말아야 하겠지요.'
},
{
id: 'fifteenth-road-to-liu-biao',
bgm: 'battle-prep',
chapter: '다음 의탁의 그림자',
background: 'story-liu-bei',
speaker: '유비',
portrait: 'liuBei',
text: '큰 깃발 아래에서도 우리의 뜻은 작아져서는 안 된다. 원소의 군영에서 기회를 살피고, 다시 백성을 지킬 길을 찾아야 한다.'
}
];
export const firstBattleMap: BattleMap = {
width: 20,
height: 18,
@@ -1170,6 +1235,12 @@ export const fourteenthBattleMap: BattleMap = {
terrain: createFourteenthBattleTerrain()
};
export const fifteenthBattleMap: BattleMap = {
width: 34,
height: 24,
terrain: createFifteenthBattleTerrain()
};
export const firstBattleUnits: UnitData[] = [
{
id: 'liu-bei',
@@ -5004,6 +5075,378 @@ export const fourteenthBattleUnits: UnitData[] = [
}
];
const fifteenthBattleAllyPositions: Record<string, { x: number; y: number }> = {
'liu-bei': { x: 3, y: 18 },
'guan-yu': { x: 4, y: 17 },
'zhang-fei': { x: 4, y: 19 },
'jian-yong': { x: 5, y: 18 },
'mi-zhu': { x: 3, y: 20 },
'sun-qian': { x: 5, y: 20 }
};
export const fifteenthBattleUnits: UnitData[] = [
...[...firstBattleUnits.filter((unit) => unit.faction === 'ally'), ...xuzhouRecruitUnits, ...caoBreakRecruitUnits].map((unit) =>
placeScenarioUnit(unit, fifteenthBattleAllyPositions[unit.id] ?? { x: unit.x, y: unit.y })
),
{
id: 'yuan-refuge-scout-a',
name: '조조군 척후',
faction: 'enemy',
className: '추격 척후',
classKey: 'bandit',
level: 15,
exp: 92,
hp: 54,
maxHp: 54,
attack: 20,
move: 4,
stats: { might: 90, intelligence: 62, leadership: 68, agility: 88, luck: 62 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 68 },
armor: { itemId: 'rebel-vest', level: 2, exp: 30 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
},
x: 10,
y: 16
},
{
id: 'yuan-refuge-scout-b',
name: '조조군 척후',
faction: 'enemy',
className: '추격 척후',
classKey: 'bandit',
level: 15,
exp: 92,
hp: 54,
maxHp: 54,
attack: 20,
move: 4,
stats: { might: 90, intelligence: 62, leadership: 68, agility: 88, luck: 62 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 68 },
armor: { itemId: 'rebel-vest', level: 2, exp: 30 },
accessory: { itemId: 'grain-pouch', level: 1, exp: 0 }
},
x: 12,
y: 20
},
{
id: 'yuan-refuge-infantry-a',
name: '조조 보병',
faction: 'enemy',
className: '추격 보병',
classKey: 'yellowTurban',
level: 15,
exp: 94,
hp: 62,
maxHp: 62,
attack: 20,
move: 3,
stats: { might: 92, intelligence: 62, leadership: 80, agility: 70, luck: 61 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 70 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 32 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
},
x: 15,
y: 12
},
{
id: 'yuan-refuge-infantry-b',
name: '조조 보병',
faction: 'enemy',
className: '추격 보병',
classKey: 'yellowTurban',
level: 15,
exp: 94,
hp: 62,
maxHp: 62,
attack: 20,
move: 3,
stats: { might: 92, intelligence: 62, leadership: 80, agility: 70, luck: 61 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 70 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 32 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
},
x: 17,
y: 14
},
{
id: 'yuan-refuge-infantry-c',
name: '조조 보병',
faction: 'enemy',
className: '추격 보병',
classKey: 'yellowTurban',
level: 16,
exp: 96,
hp: 64,
maxHp: 64,
attack: 21,
move: 3,
stats: { might: 94, intelligence: 63, leadership: 82, agility: 71, luck: 62 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 72 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 34 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
},
x: 20,
y: 10
},
{
id: 'yuan-refuge-infantry-d',
name: '조조 보병',
faction: 'enemy',
className: '추격 보병',
classKey: 'yellowTurban',
level: 16,
exp: 96,
hp: 64,
maxHp: 64,
attack: 21,
move: 3,
stats: { might: 94, intelligence: 63, leadership: 82, agility: 71, luck: 62 },
equipment: {
weapon: { itemId: 'yellow-turban-saber', level: 2, exp: 72 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 34 },
accessory: { itemId: 'yellow-scarf-charm', level: 1, exp: 0 }
},
x: 23,
y: 15
},
{
id: 'yuan-refuge-archer-a',
name: '조조 궁병',
faction: 'enemy',
className: '추격 궁병',
classKey: 'archer',
level: 15,
exp: 94,
hp: 50,
maxHp: 50,
attack: 20,
move: 3,
stats: { might: 83, intelligence: 74, leadership: 72, agility: 84, luck: 62 },
equipment: {
weapon: { itemId: 'short-bow', level: 2, exp: 68 },
armor: { itemId: 'cloth-armor', level: 2, exp: 30 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 18,
y: 8
},
{
id: 'yuan-refuge-archer-b',
name: '조조 궁병',
faction: 'enemy',
className: '추격 궁병',
classKey: 'archer',
level: 15,
exp: 94,
hp: 50,
maxHp: 50,
attack: 20,
move: 3,
stats: { might: 83, intelligence: 74, leadership: 72, agility: 84, luck: 62 },
equipment: {
weapon: { itemId: 'short-bow', level: 2, exp: 68 },
armor: { itemId: 'cloth-armor', level: 2, exp: 30 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 23,
y: 8
},
{
id: 'yuan-refuge-archer-c',
name: '조조 궁병',
faction: 'enemy',
className: '추격 궁병',
classKey: 'archer',
level: 16,
exp: 96,
hp: 52,
maxHp: 52,
attack: 21,
move: 3,
stats: { might: 84, intelligence: 75, leadership: 73, agility: 85, luck: 63 },
equipment: {
weapon: { itemId: 'short-bow', level: 2, exp: 70 },
armor: { itemId: 'cloth-armor', level: 2, exp: 32 },
accessory: { itemId: 'wind-quiver', level: 1, exp: 0 }
},
x: 27,
y: 13
},
{
id: 'yuan-refuge-cavalry-a',
name: '조조 기병',
faction: 'enemy',
className: '추격 기병',
classKey: 'cavalry',
level: 16,
exp: 98,
hp: 68,
maxHp: 68,
attack: 23,
move: 5,
stats: { might: 98, intelligence: 58, leadership: 76, agility: 92, luck: 64 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 72 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 34 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 14,
y: 18
},
{
id: 'yuan-refuge-cavalry-b',
name: '조조 기병',
faction: 'enemy',
className: '추격 기병',
classKey: 'cavalry',
level: 16,
exp: 98,
hp: 68,
maxHp: 68,
attack: 23,
move: 5,
stats: { might: 98, intelligence: 58, leadership: 76, agility: 92, luck: 64 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 72 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 34 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 19,
y: 19
},
{
id: 'yuan-refuge-cavalry-c',
name: '조조 기병',
faction: 'enemy',
className: '추격 기병',
classKey: 'cavalry',
level: 16,
exp: 98,
hp: 68,
maxHp: 68,
attack: 23,
move: 5,
stats: { might: 98, intelligence: 58, leadership: 76, agility: 92, luck: 64 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 72 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 34 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 25,
y: 19
},
{
id: 'yuan-refuge-guard-a',
name: '북상로 수비대',
faction: 'enemy',
className: '관문 수비대',
classKey: 'yellowTurban',
level: 16,
exp: 98,
hp: 70,
maxHp: 70,
attack: 22,
move: 3,
stats: { might: 96, intelligence: 64, leadership: 84, agility: 70, luck: 62 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 74 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 36 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 27,
y: 9
},
{
id: 'yuan-refuge-guard-b',
name: '북상로 수비대',
faction: 'enemy',
className: '관문 수비대',
classKey: 'yellowTurban',
level: 16,
exp: 98,
hp: 70,
maxHp: 70,
attack: 22,
move: 3,
stats: { might: 96, intelligence: 64, leadership: 84, agility: 70, luck: 62 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 74 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 36 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 29,
y: 12
},
{
id: 'yuan-refuge-guard-c',
name: '북상로 수비대',
faction: 'enemy',
className: '관문 수비대',
classKey: 'yellowTurban',
level: 16,
exp: 98,
hp: 70,
maxHp: 70,
attack: 22,
move: 3,
stats: { might: 96, intelligence: 64, leadership: 84, agility: 70, luck: 62 },
equipment: {
weapon: { itemId: 'iron-spear', level: 2, exp: 74 },
armor: { itemId: 'lamellar-armor', level: 2, exp: 36 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 30,
y: 15
},
{
id: 'yuan-refuge-strategist-a',
name: '조조군 참모',
faction: 'enemy',
className: '추격 참모',
classKey: 'archer',
level: 16,
exp: 98,
hp: 54,
maxHp: 54,
attack: 22,
move: 3,
stats: { might: 70, intelligence: 88, leadership: 78, agility: 76, luck: 66 },
equipment: {
weapon: { itemId: 'short-bow', level: 2, exp: 72 },
armor: { itemId: 'cloth-armor', level: 2, exp: 34 },
accessory: { itemId: 'war-manual', level: 1, exp: 0 }
},
x: 26,
y: 11
},
{
id: 'yuan-refuge-leader-cai-yang',
name: '채양',
faction: 'enemy',
className: '조조군 추격장',
classKey: 'rebelLeader',
level: 18,
exp: 12,
hp: 92,
maxHp: 92,
attack: 26,
move: 4,
stats: { might: 104, intelligence: 58, leadership: 90, agility: 82, luck: 64 },
equipment: {
weapon: { itemId: 'iron-spear', level: 3, exp: 18 },
armor: { itemId: 'reinforced-lamellar', level: 2, exp: 42 },
accessory: { itemId: 'bravery-token', level: 1, exp: 0 }
},
x: 31,
y: 12
}
];
export const firstBattleBonds: BattleBond[] = [
{
id: 'liu-bei__guan-yu',
@@ -5084,6 +5527,7 @@ export const thirteenthBattleBonds: BattleBond[] = twelfthBattleBonds.map(cloneB
export const fourteenthBattleBonds: BattleBond[] = [...thirteenthBattleBonds, ...caoBreakRecruitBonds].map(
cloneBattleBondForScenario
);
export const fifteenthBattleBonds: BattleBond[] = fourteenthBattleBonds.map(cloneBattleBondForScenario);
function createEighthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 22 }, (_, y) =>
@@ -5388,6 +5832,58 @@ function createFourteenthBattleTerrain(): TerrainType[][] {
);
}
function createFifteenthBattleTerrain(): TerrainType[][] {
return Array.from({ length: 24 }, (_, y) =>
Array.from({ length: 34 }, (_, x): TerrainType => {
if (x <= 2 && y >= 17 && y <= 21) {
return 'camp';
}
if ((x >= 27 && x <= 32 && y >= 8 && y <= 15) || (x >= 30 && x <= 33 && y >= 5 && y <= 7)) {
return 'fort';
}
if ((x >= 9 && x <= 11 && y >= 15 && y <= 17) || (x >= 20 && x <= 22 && y >= 7 && y <= 9)) {
return 'village';
}
if ((x === 14 || x === 15) && y >= 1 && y <= 22) {
return 'river';
}
if ((x === 24 || x === 25) && y >= 2 && y <= 20) {
return 'river';
}
if (
(y === 18 && x >= 2 && x <= 17) ||
(y === 14 && x >= 13 && x <= 28) ||
(y === 10 && x >= 21 && x <= 32) ||
(x === 29 && y >= 8 && y <= 17)
) {
return 'road';
}
if ((x >= 5 && x <= 17 && y === 23 - x) || (x >= 12 && x <= 28 && y === x - 7)) {
return 'road';
}
if (
(x <= 8 && y <= 7) ||
(x >= 4 && x <= 11 && y >= 9 && y <= 13) ||
(x >= 2 && x <= 8 && y >= 14 && y <= 16) ||
(x >= 27 && y >= 18 && y <= 22)
) {
return 'forest';
}
if (
(x >= 6 && x <= 12 && y >= 2 && y <= 5) ||
(x >= 18 && x <= 22 && y >= 18 && y <= 22) ||
(x >= 29 && x <= 33 && y >= 1 && y <= 4)
) {
return 'hill';
}
if ((x >= 32 && y <= 8) || (x >= 32 && y >= 16) || (x <= 1 && y <= 4)) {
return 'cliff';
}
return 'plain';
})
);
}
function placeScenarioUnit(unit: UnitData, position: { x: number; y: number }): UnitData {
return {
...cloneUnitForScenario(unit),