Rework first battle story as unit cutscenes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { TerrainType, UnitClassKey } from './battleRules';
|
||||
import type { EquipmentSet } from './battleItems';
|
||||
import type { StoryCutscene } from './storyCutscenes';
|
||||
|
||||
export type PortraitKey = string;
|
||||
|
||||
@@ -11,6 +12,7 @@ export type StoryPage = {
|
||||
portrait?: PortraitKey;
|
||||
bgm?: string;
|
||||
text: string;
|
||||
cutscene?: StoryCutscene;
|
||||
};
|
||||
|
||||
export type UnitData = {
|
||||
@@ -185,6 +187,201 @@ export const firstBattleVictoryPages: StoryPage[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const firstBattleStoryOverrides: Record<string, Partial<StoryPage>> = {
|
||||
'militia-rises': {
|
||||
chapter: '탁현 의용군',
|
||||
speaker: '장비',
|
||||
text: '창을 들 사람은 앞으로 나오시오. 오늘 모인 이름 없는 무리가 마을의 방패가 될 것이오.',
|
||||
cutscene: {
|
||||
kind: 'muster',
|
||||
title: '탁현 의용군 집결',
|
||||
subtitle: '세 형제와 마을 사람들이 첫 출전을 준비합니다.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.34, y: 0.53, size: 122, direction: 'east', label: '군주' },
|
||||
{ unitId: 'guan-yu', x: 0.49, y: 0.52, size: 132, direction: 'east', label: '선봉' },
|
||||
{ unitId: 'zhang-fei', x: 0.64, y: 0.53, size: 132, direction: 'west', label: '돌파' }
|
||||
],
|
||||
briefing: {
|
||||
title: '출전 준비',
|
||||
lines: ['유비는 중앙에서 전열을 잡습니다.', '관우는 앞길을 열고, 장비는 측면을 찌릅니다.']
|
||||
},
|
||||
rewards: [
|
||||
{ label: '아군: 유비 · 관우 · 장비', tone: 'bond' },
|
||||
{ label: '목표: 마을 방어', tone: 'next' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-sortie': {
|
||||
chapter: '첫 출전',
|
||||
speaker: '유비',
|
||||
text: '오늘은 마을을 지키는 첫 싸움이다. 서로의 등을 믿고, 백성들이 물러설 시간을 벌자.',
|
||||
cutscene: {
|
||||
kind: 'muster',
|
||||
title: '세 형제 출진',
|
||||
subtitle: '전투에서 조작할 유닛이 스토리 화면에 그대로 등장합니다.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.32, y: 0.53, size: 136, direction: 'east', label: '유비' },
|
||||
{ unitId: 'guan-yu', x: 0.5, y: 0.51, size: 144, direction: 'east', label: '관우' },
|
||||
{ unitId: 'zhang-fei', x: 0.68, y: 0.53, size: 144, direction: 'west', label: '장비' }
|
||||
],
|
||||
briefing: {
|
||||
title: '전장 역할',
|
||||
lines: ['유비: 생존과 지휘', '관우: 전면 돌파', '장비: 측면 압박']
|
||||
},
|
||||
rewards: [
|
||||
{ label: '패배 조건: 유비 퇴각 금지', tone: 'next' },
|
||||
{ label: '공명: 세 형제 인접 운용', tone: 'bond' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'battle-briefing': {
|
||||
chapter: '탁현 작전판',
|
||||
speaker: '전황',
|
||||
text: '황건 두령을 쓰러뜨리면 적의 전열이 무너진다. 마을 입구를 지키며 북쪽 적진으로 밀고 올라가자.',
|
||||
cutscene: {
|
||||
kind: 'operation',
|
||||
title: '탁현의 전투',
|
||||
subtitle: '마을 입구에서 시작해 황건 두령을 격파하십시오.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.24, y: 0.54, size: 104, direction: 'east', label: '중앙' },
|
||||
{ unitId: 'guan-yu', x: 0.34, y: 0.53, size: 108, direction: 'east', label: '전면' },
|
||||
{ unitId: 'zhang-fei', x: 0.44, y: 0.54, size: 108, direction: 'east', label: '측면' },
|
||||
{ unitId: 'rebel-leader', x: 0.78, y: 0.53, size: 108, direction: 'west', label: '두령' }
|
||||
],
|
||||
markers: [
|
||||
{ x: 0.2, y: 0.74, label: '아군 시작', side: 'ally' },
|
||||
{ x: 0.44, y: 0.5, label: '마을 입구', side: 'objective' },
|
||||
{ x: 0.74, y: 0.28, label: '황건 두령', side: 'enemy' }
|
||||
],
|
||||
briefing: {
|
||||
title: '승리 조건',
|
||||
lines: ['황건 두령 격파', '유비 생존', '마을 지형 확보']
|
||||
},
|
||||
rewards: [
|
||||
{ label: '보상: 보급품 · 연습검 · 의용군 명성', tone: 'reward' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-victory-village': {
|
||||
chapter: '첫 승리',
|
||||
speaker: '전황',
|
||||
text: '황건의 깃발이 꺾이고, 흩어졌던 백성들이 마을로 돌아오기 시작했다.',
|
||||
cutscene: {
|
||||
kind: 'victory',
|
||||
title: '탁현 방어 성공',
|
||||
subtitle: '전투에서 싸운 세 유닛이 그대로 승리 장면에 남습니다.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.36, y: 0.53, size: 132, direction: 'east', label: '유비' },
|
||||
{ unitId: 'guan-yu', x: 0.52, y: 0.52, size: 142, direction: 'west', label: '관우' },
|
||||
{ unitId: 'zhang-fei', x: 0.67, y: 0.53, size: 142, direction: 'west', label: '장비' }
|
||||
],
|
||||
rewards: [
|
||||
{ label: '승리 조건 달성', tone: 'reward' },
|
||||
{ label: '마을 방어', tone: 'bond' },
|
||||
{ label: '다음: 황건 잔당 추격', tone: 'next' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-victory-liu-bei': {
|
||||
chapter: '첫 승리',
|
||||
speaker: '유비',
|
||||
text: '오늘의 승리는 작다. 하지만 백성을 지키는 길은 여기서 시작된다.',
|
||||
cutscene: {
|
||||
kind: 'victory',
|
||||
title: '전투 후 정비',
|
||||
subtitle: '획득 보상은 다음 출전 준비와 장비 화면으로 이어집니다.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.44, y: 0.52, size: 146, direction: 'south', label: '유비' },
|
||||
{ unitId: 'guan-yu', x: 0.58, y: 0.53, size: 130, direction: 'west', label: '관우' },
|
||||
{ unitId: 'zhang-fei', x: 0.7, y: 0.54, size: 130, direction: 'west', label: '장비' }
|
||||
],
|
||||
rewards: [
|
||||
{ label: '보급품: 콩 · 탁주', tone: 'reward' },
|
||||
{ label: '장비: 연습검', tone: 'reward' },
|
||||
{ label: '명성: 의용군 +1', tone: 'bond' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-victory-guan-yu': {
|
||||
chapter: '의용군의 이름',
|
||||
speaker: '관우',
|
||||
text: '형님의 뜻을 보고 사람들이 모일 것입니다. 다음 싸움은 오늘보다 무거운 책임을 요구하겠지요.',
|
||||
cutscene: {
|
||||
kind: 'victory',
|
||||
title: '관우의 다짐',
|
||||
subtitle: '선봉의 장병기가 다음 전투의 전열을 맡습니다.',
|
||||
actors: [
|
||||
{ unitId: 'guan-yu', x: 0.45, y: 0.52, size: 152, direction: 'east', label: '선봉' },
|
||||
{ unitId: 'liu-bei', x: 0.31, y: 0.54, size: 122, direction: 'east', label: '지휘' },
|
||||
{ unitId: 'zhang-fei', x: 0.64, y: 0.54, size: 128, direction: 'west', label: '돌파' }
|
||||
],
|
||||
rewards: [
|
||||
{ label: '출전 가능: 관우', tone: 'bond' },
|
||||
{ label: '역할 추천: 전면', tone: 'next' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-victory-zhang-fei': {
|
||||
chapter: '의용군의 이름',
|
||||
speaker: '장비',
|
||||
text: '좋다! 다음에는 더 크게 몰아치자. 창을 드는 자들은 우리 의용군을 따라올 것이다.',
|
||||
cutscene: {
|
||||
kind: 'victory',
|
||||
title: '장비의 기세',
|
||||
subtitle: '측면 돌파와 압박이 다음 전장의 선택지가 됩니다.',
|
||||
actors: [
|
||||
{ unitId: 'zhang-fei', x: 0.48, y: 0.52, size: 154, direction: 'east', label: '돌파' },
|
||||
{ unitId: 'liu-bei', x: 0.34, y: 0.54, size: 122, direction: 'east', label: '지휘' },
|
||||
{ unitId: 'guan-yu', x: 0.64, y: 0.53, size: 130, direction: 'west', label: '선봉' }
|
||||
],
|
||||
rewards: [
|
||||
{ label: '출전 가능: 장비', tone: 'bond' },
|
||||
{ label: '역할 추천: 측면', tone: 'next' }
|
||||
]
|
||||
}
|
||||
},
|
||||
'first-victory-next': {
|
||||
chapter: '다음 길',
|
||||
speaker: '전황',
|
||||
text: '세 형제의 이름은 탁현 일대에 퍼지기 시작했다. 이제 황건 잔당의 퇴로를 끊어야 한다.',
|
||||
cutscene: {
|
||||
kind: 'operation',
|
||||
title: '다음 전투 해금',
|
||||
subtitle: '황건 잔당 추격으로 이어집니다.',
|
||||
actors: [
|
||||
{ unitId: 'liu-bei', x: 0.25, y: 0.54, size: 108, direction: 'east', label: '유비' },
|
||||
{ unitId: 'guan-yu', x: 0.34, y: 0.53, size: 112, direction: 'east', label: '관우' },
|
||||
{ unitId: 'zhang-fei', x: 0.43, y: 0.54, size: 112, direction: 'east', label: '장비' }
|
||||
],
|
||||
markers: [
|
||||
{ x: 0.24, y: 0.68, label: '탁현', side: 'ally' },
|
||||
{ x: 0.58, y: 0.46, label: '북쪽 길목', side: 'objective' },
|
||||
{ x: 0.76, y: 0.32, label: '황건 잔당', side: 'enemy' }
|
||||
],
|
||||
briefing: {
|
||||
title: '정비 후 출전',
|
||||
lines: ['보급품 배정', '장비 교체', '출전 무장 선택']
|
||||
},
|
||||
rewards: [
|
||||
{ label: '군영에서 보상 확인', tone: 'reward' },
|
||||
{ label: '다음: 황건 잔당 추격', tone: 'next' }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function applyStoryPageOverrides(pages: StoryPage[]) {
|
||||
pages.forEach((page) => {
|
||||
const override = firstBattleStoryOverrides[page.id];
|
||||
if (override) {
|
||||
Object.assign(page, override);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyStoryPageOverrides(prologuePages);
|
||||
applyStoryPageOverrides(firstBattleVictoryPages);
|
||||
|
||||
export const secondBattleIntroPages: StoryPage[] = [
|
||||
{
|
||||
id: 'second-pursuit-rumor',
|
||||
|
||||
100
src/game/data/storyCutscenes.ts
Normal file
100
src/game/data/storyCutscenes.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { UnitDirection } from './unitAssets';
|
||||
|
||||
export type StoryCutsceneKind = 'muster' | 'operation' | 'victory';
|
||||
export type StoryCutsceneTone = 'reward' | 'next' | 'bond';
|
||||
export type StoryCutsceneMarkerSide = 'ally' | 'enemy' | 'objective';
|
||||
|
||||
export type StoryCutsceneActorProfile = {
|
||||
unitId: string;
|
||||
name: string;
|
||||
roleLabel: string;
|
||||
unitTextureKey: string;
|
||||
portraitKey: string;
|
||||
color: number;
|
||||
};
|
||||
|
||||
export type StoryCutsceneActor = {
|
||||
unitId: string;
|
||||
x: number;
|
||||
y: number;
|
||||
size?: number;
|
||||
direction?: UnitDirection;
|
||||
label?: string;
|
||||
line?: string;
|
||||
};
|
||||
|
||||
export type StoryCutsceneMarker = {
|
||||
x: number;
|
||||
y: number;
|
||||
label: string;
|
||||
side: StoryCutsceneMarkerSide;
|
||||
};
|
||||
|
||||
export type StoryCutsceneBriefing = {
|
||||
title: string;
|
||||
lines: string[];
|
||||
};
|
||||
|
||||
export type StoryCutsceneReward = {
|
||||
label: string;
|
||||
tone?: StoryCutsceneTone;
|
||||
};
|
||||
|
||||
export type StoryCutscene = {
|
||||
kind: StoryCutsceneKind;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
actors?: StoryCutsceneActor[];
|
||||
briefing?: StoryCutsceneBriefing;
|
||||
markers?: StoryCutsceneMarker[];
|
||||
rewards?: StoryCutsceneReward[];
|
||||
};
|
||||
|
||||
const storyCutsceneActorProfiles: Record<string, StoryCutsceneActorProfile> = {
|
||||
'liu-bei': {
|
||||
unitId: 'liu-bei',
|
||||
name: '유비',
|
||||
roleLabel: '검을 든 군주',
|
||||
unitTextureKey: 'unit-liu-bei',
|
||||
portraitKey: 'liuBei',
|
||||
color: 0xd8b15f
|
||||
},
|
||||
'guan-yu': {
|
||||
unitId: 'guan-yu',
|
||||
name: '관우',
|
||||
roleLabel: '장병기 선봉',
|
||||
unitTextureKey: 'unit-guan-yu',
|
||||
portraitKey: 'guanYu',
|
||||
color: 0x74c476
|
||||
},
|
||||
'zhang-fei': {
|
||||
unitId: 'zhang-fei',
|
||||
name: '장비',
|
||||
roleLabel: '돌파형 창병',
|
||||
unitTextureKey: 'unit-zhang-fei',
|
||||
portraitKey: 'zhangFei',
|
||||
color: 0xd46a4c
|
||||
},
|
||||
'rebel-leader': {
|
||||
unitId: 'rebel-leader',
|
||||
name: '황건 두령',
|
||||
roleLabel: '격파 목표',
|
||||
unitTextureKey: 'unit-rebel-leader',
|
||||
portraitKey: 'rebelLeader',
|
||||
color: 0xd6b852
|
||||
}
|
||||
};
|
||||
|
||||
export function storyCutsceneActorProfileFor(unitId: string) {
|
||||
return storyCutsceneActorProfiles[unitId];
|
||||
}
|
||||
|
||||
export function storyCutsceneActorTextureKeys(cutscene?: StoryCutscene) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
(cutscene?.actors ?? [])
|
||||
.map((actor) => storyCutsceneActorProfileFor(actor.unitId)?.unitTextureKey)
|
||||
.filter((textureKey): textureKey is string => Boolean(textureKey))
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user