feat: add playable Zhuo prologue village

This commit is contained in:
2026-07-24 01:33:36 +09:00
parent b7c90c9452
commit df1322b1f0
17 changed files with 2418 additions and 40 deletions

View File

@@ -0,0 +1,204 @@
import { prologuePages, type StoryPage } from './scenario';
export const prologueVillageId = 'zhuo-prologue';
export const prologueVillageRequiredObjectiveIds = [
'meet-zhang-fei',
'meet-guan-yu',
'check-supplies'
] as const;
export type PrologueVillageRequiredObjectiveId =
(typeof prologueVillageRequiredObjectiveIds)[number];
export const prologueVillageObjectiveDefinitions: ReadonlyArray<{
id: PrologueVillageRequiredObjectiveId | 'make-oath';
label: string;
shortLabel: string;
location: string;
}> = [
{
id: 'meet-zhang-fei',
label: '장터 주점에서 장비와 이야기하기',
shortLabel: '장비와 대화',
location: '서쪽 주점'
},
{
id: 'meet-guan-yu',
label: '동쪽 장터에서 관우의 정찰 듣기',
shortLabel: '관우와 대화',
location: '동쪽 장터'
},
{
id: 'check-supplies',
label: '의병소에서 무기와 보급 확인하기',
shortLabel: '출전 준비',
location: '남동쪽 의병소'
},
{
id: 'make-oath',
label: '복숭아 동산에서 두 사람과 결의하기',
shortLabel: '도원결의',
location: '북쪽 복숭아 동산'
}
];
export type PrologueVillageDialogueLine = {
speaker: string;
text: string;
textureKey?: string;
};
export type PrologueVillageNpcDefinition = {
id: string;
name: string;
role: string;
textureKey: string;
x: number;
y: number;
direction: 'south' | 'east' | 'north' | 'west';
objectiveId?: PrologueVillageRequiredObjectiveId;
dialogue: PrologueVillageDialogueLine[];
repeatDialogue?: PrologueVillageDialogueLine[];
};
export const prologueVillageNpcDefinitions: readonly PrologueVillageNpcDefinition[] = [
{
id: 'zhang-fei',
name: '장비',
role: '뜻을 함께할 호걸',
textureKey: 'unit-zhang-fei',
x: 360,
y: 495,
direction: 'east',
objectiveId: 'meet-zhang-fei',
dialogue: [
{
speaker: '장비',
textureKey: 'unit-zhang-fei',
text: '나라가 어지러운데 뜻 있는 사내가 어찌 팔짱만 끼고 있겠소! 내 재산을 내어 의병을 모으겠소.'
},
{
speaker: '유비',
textureKey: 'unit-liu-bei',
text: '백성을 지키려는 마음이 같다면 힘을 합칩시다. 먼저 싸울 사람과 물자를 갖추어야 하오.'
},
{
speaker: '장비',
textureKey: 'unit-zhang-fei',
text: '좋소! 내가 장터 사람들을 모아 두겠소. 의병소의 준비를 확인한 뒤 복숭아 동산에서 다시 만납시다.'
}
],
repeatDialogue: [
{
speaker: '장비',
textureKey: 'unit-zhang-fei',
text: '사람들에게 뜻을 전하고 있소. 관우와 의병소도 둘러본 뒤 동산으로 오시오!'
}
]
},
{
id: 'guan-yu',
name: '관우',
role: '길목을 살피는 무인',
textureKey: 'unit-guan-yu',
x: 1110,
y: 460,
direction: 'west',
objectiveId: 'meet-guan-yu',
dialogue: [
{
speaker: '관우',
textureKey: 'unit-guan-yu',
text: '북쪽 길을 살펴보니 황건 잔당이 마을 어귀로 내려오고 있습니다. 더 늦으면 백성이 위험합니다.'
},
{
speaker: '유비',
textureKey: 'unit-liu-bei',
text: '우리가 먼저 길목을 막아야겠군요. 함께 백성을 구하는 길을 열어 주시겠소?'
},
{
speaker: '관우',
textureKey: 'unit-guan-yu',
text: '뜻이 올바르다면 천 리라도 함께하겠습니다. 준비를 마치고 복숭아 동산에서 맹세하지요.'
}
],
repeatDialogue: [
{
speaker: '관우',
textureKey: 'unit-guan-yu',
text: '적은 보병을 앞세우고 뒤에서 활을 쏠 것입니다. 준비가 끝나는 대로 북문으로 나가야 합니다.'
}
]
},
{
id: 'quartermaster',
name: '의병소 관리인',
role: '무기와 보급 담당',
textureKey: 'unit-shu-officer',
x: 1045,
y: 760,
direction: 'east',
objectiveId: 'check-supplies',
dialogue: [
{
speaker: '의병소 관리인',
textureKey: 'unit-shu-officer',
text: '장비 공이 맡긴 돈으로 창과 연습검을 손질했습니다. 콩과 상처약도 각 조에 나누어 두었습니다.'
},
{
speaker: '유비',
textureKey: 'unit-liu-bei',
text: '좋습니다. 무기보다 먼저 사람을 살피고, 다친 이는 곧바로 뒤로 물리도록 전해 주시오.'
},
{
speaker: '의병소 관리인',
textureKey: 'unit-shu-officer',
text: '출전 준비를 마쳤습니다. 이제 동료들과 결의하면 북문을 열겠습니다.'
}
],
repeatDialogue: [
{
speaker: '의병소 관리인',
textureKey: 'unit-shu-officer',
text: '무기와 보급은 모두 나누었습니다. 북문을 나서기 전에 동산에서 동료들과 합류하십시오.'
}
]
},
{
id: 'market-villager',
name: '탁현 주민',
role: '장터 소식',
textureKey: 'unit-shu-infantry',
x: 745,
y: 620,
direction: 'south',
dialogue: [
{
speaker: '탁현 주민',
textureKey: 'unit-shu-infantry',
text: '서쪽 주점의 장비 공과 동쪽 장터의 긴 수염 무인이 의병 이야기를 나누고 있었습니다.'
}
]
}
];
const openingPageIds = new Set(['yellow-turban-chaos', 'liu-bei-resolve']);
const departurePageIds = new Set([
'first-sortie',
'yellow-turban-nearby',
'first-battle-plan-talk',
'battle-briefing'
]);
export function prologueOpeningPages(): StoryPage[] {
return selectProloguePages(openingPageIds);
}
export function prologueDeparturePages(): StoryPage[] {
return selectProloguePages(departurePageIds);
}
function selectProloguePages(pageIds: ReadonlySet<string>) {
return prologuePages.filter((page) => pageIds.has(page.id));
}