feat(story): Phase 9 스토리/엔딩 시스템 구현

- story_data.dart: 5개 Act 스토리 텍스트 및 ASCII 아트
- story_service.dart: Act 전환/보스 조우/엔딩 이벤트 관리
- cinematic_view.dart: 풀스크린 시네마틱 UI (페이드, 스킵)
- game_play_screen.dart: 레벨 기반 Act 전환 시 시네마틱 재생
This commit is contained in:
JiWoong Sul
2025-12-17 18:38:08 +09:00
parent 8cbef3475b
commit abcb89d334
4 changed files with 902 additions and 0 deletions

395
lib/data/story_data.dart Normal file
View File

@@ -0,0 +1,395 @@
/// Phase 9: 스토리 데이터 (Story Data)
///
/// 프롤로그부터 엔딩까지 일관된 스토리 텍스트 정의
library;
/// 스토리 Act 정의
enum StoryAct {
prologue, // 프롤로그
act1, // Act I: 각성 (레벨 1-20)
act2, // Act II: 성장 (레벨 21-40)
act3, // Act III: 시련 (레벨 41-60)
act4, // Act IV: 결전 (레벨 61-80)
act5, // Act V: 종말 (레벨 81-100)
ending, // 엔딩
}
/// Act별 레벨 범위 (Level Range)
const Map<StoryAct, (int, int)> actLevelRange = {
StoryAct.prologue: (1, 1),
StoryAct.act1: (1, 20),
StoryAct.act2: (21, 40),
StoryAct.act3: (41, 60),
StoryAct.act4: (61, 80),
StoryAct.act5: (81, 100),
StoryAct.ending: (100, 999),
};
/// 레벨로 현재 Act 계산 (Calculate Current Act from Level)
StoryAct getActForLevel(int level) {
if (level <= 0) return StoryAct.prologue;
if (level <= 20) return StoryAct.act1;
if (level <= 40) return StoryAct.act2;
if (level <= 60) return StoryAct.act3;
if (level <= 80) return StoryAct.act4;
if (level < 100) return StoryAct.act5;
return StoryAct.ending;
}
/// 시네마틱 단계 (Cinematic Step)
class CinematicStep {
const CinematicStep({
required this.text,
this.asciiArt,
this.durationMs = 3000,
});
final String text;
final String? asciiArt;
final int durationMs;
}
/// Act별 시네마틱 데이터 (Cinematic Data per Act)
const Map<StoryAct, List<CinematicStep>> cinematicData = {
// 프롤로그: 코드의 신으로부터 비전을 받음
StoryAct.prologue: [
CinematicStep(
text: 'In the beginning, there was only the Void...',
asciiArt: _asciiVoid,
durationMs: 4000,
),
CinematicStep(
text: 'Then came the First Commit, and Light filled the Codebase.',
asciiArt: _asciiLight,
durationMs: 4000,
),
CinematicStep(
text: 'The Code God spoke: "Let there be Functions."',
durationMs: 3500,
),
CinematicStep(
text: 'And so the Digital Realm was born...',
durationMs: 3000,
),
CinematicStep(
text: 'But from the shadows emerged the Glitch.',
asciiArt: _asciiGlitch,
durationMs: 4000,
),
CinematicStep(
text: 'Now, a new hero awakens to defend the Code.',
durationMs: 3500,
),
CinematicStep(
text: 'Your journey begins...',
durationMs: 2500,
),
],
// Act I: 각성 (레벨 1-20)
StoryAct.act1: [
CinematicStep(
text: '=== ACT I: AWAKENING ===',
durationMs: 3000,
),
CinematicStep(
text: 'You have proven yourself against the lesser bugs.',
durationMs: 3000,
),
CinematicStep(
text: 'The Debugger Knights take notice of your potential.',
durationMs: 3500,
),
CinematicStep(
text: 'But a greater threat lurks in the Bug Nest...',
asciiArt: _asciiBugNest,
durationMs: 4000,
),
CinematicStep(
text: 'The Syntax Error Dragon awaits.',
asciiArt: _asciiDragon,
durationMs: 4000,
),
],
// Act II: 성장 (레벨 21-40)
StoryAct.act2: [
CinematicStep(
text: '=== ACT II: GROWTH ===',
durationMs: 3000,
),
CinematicStep(
text: 'With the Dragon slain, you join the Debugger Knights.',
durationMs: 3500,
),
CinematicStep(
text: 'The Corrupted Network spreads its infection...',
asciiArt: _asciiNetwork,
durationMs: 4000,
),
CinematicStep(
text: 'A traitor among the Knights is revealed!',
durationMs: 3500,
),
CinematicStep(
text: 'The Memory Leak Hydra threatens all data.',
asciiArt: _asciiHydra,
durationMs: 4000,
),
CinematicStep(
text: 'You must stop the corruption before it consumes everything.',
durationMs: 3500,
),
],
// Act III: 시련 (레벨 41-60)
StoryAct.act3: [
CinematicStep(
text: '=== ACT III: TRIALS ===',
durationMs: 3000,
),
CinematicStep(
text: 'The path leads to the Null Kingdom...',
asciiArt: _asciiNullKingdom,
durationMs: 4000,
),
CinematicStep(
text: 'The Ancient Compiler challenges you to its trials.',
durationMs: 3500,
),
CinematicStep(
text: 'A companion falls... their sacrifice not in vain.',
durationMs: 4000,
),
CinematicStep(
text: 'The Buffer Overflow Titan guards the gate.',
asciiArt: _asciiTitan,
durationMs: 4000,
),
CinematicStep(
text: 'Only through great sacrifice can you proceed.',
durationMs: 3500,
),
],
// Act IV: 결전 (레벨 61-80)
StoryAct.act4: [
CinematicStep(
text: '=== ACT IV: CONFRONTATION ===',
durationMs: 3000,
),
CinematicStep(
text: "The Glitch God's Citadel looms before you.",
asciiArt: _asciiCitadel,
durationMs: 4000,
),
CinematicStep(
text: 'Former enemies unite against the common threat.',
durationMs: 3500,
),
CinematicStep(
text: 'The Final Alliance is forged.',
durationMs: 3000,
),
CinematicStep(
text: 'The Kernel Panic Archon blocks your path.',
asciiArt: _asciiArchon,
durationMs: 4000,
),
CinematicStep(
text: 'One final battle before the end...',
durationMs: 3500,
),
],
// Act V: 종말 (레벨 81-100)
StoryAct.act5: [
CinematicStep(
text: '=== ACT V: ENDGAME ===',
durationMs: 3000,
),
CinematicStep(
text: 'The Glitch God reveals its true form.',
asciiArt: _asciiGlitchGod,
durationMs: 4000,
),
CinematicStep(
text: 'Reality itself begins to corrupt.',
durationMs: 3500,
),
CinematicStep(
text: 'All hope rests upon your shoulders.',
durationMs: 3000,
),
CinematicStep(
text: 'The final battle for the Codebase begins!',
durationMs: 3500,
),
],
// 엔딩: 시스템 재부팅, 평화 회복
StoryAct.ending: [
CinematicStep(
text: '=== THE END ===',
durationMs: 3000,
),
CinematicStep(
text: 'The Glitch God falls. The corruption fades.',
asciiArt: _asciiVictory,
durationMs: 4000,
),
CinematicStep(
text: 'System Reboot initiated...',
durationMs: 3000,
),
CinematicStep(
text: 'Peace returns to the Digital Realm.',
durationMs: 3500,
),
CinematicStep(
text: 'Your legend will be compiled into the eternal logs.',
durationMs: 4000,
),
CinematicStep(
text: 'THE END',
asciiArt: _asciiTheEnd,
durationMs: 5000,
),
CinematicStep(
text: '...or is it?',
durationMs: 3000,
),
],
};
// ============================================================================
// ASCII Art 상수 (ASCII Art Constants)
// ============================================================================
const _asciiVoid = '''
. . .
. . . . .
. . . . . .
. . . . .
. . .
''';
const _asciiLight = '''
\\|/
-- * --
/|\\
''';
const _asciiGlitch = '''
####!!####
# GLITCH #
####!!####
''';
const _asciiBugNest = '''
/\\ /\\
<( o.o )>
> ^^ <
''';
const _asciiDragon = '''
/\\___/\\
( O O )
\\ ^ /
\\~~~/ ~
''';
const _asciiNetwork = '''
[*]--[*]--[*]
| | |
[*]--[!]--[*]
| | |
[*]--[*]--[*]
''';
const _asciiHydra = '''
/\\ /\\ /\\
( O O O )
\\ \\|/ /
\\|||/
''';
const _asciiNullKingdom = '''
+--NULL--+
| ???? |
| VOID |
+--------+
''';
const _asciiTitan = '''
[####]
/| |\\
| | | |
/ |__| \\
''';
const _asciiCitadel = '''
/\\
/ \\
|GLITCH|
|======|
| |
''';
const _asciiArchon = '''
^^^^^
(|O O|)
\\===/
|X|
''';
const _asciiGlitchGod = '''
##########
# G L I #
# T C H #
# GOD #
##########
''';
const _asciiVictory = '''
\\O/
|
/ \\
VICTORY!
''';
const _asciiTheEnd = '''
+-----------+
| THE END |
+-----------+
''';
/// Act별 보스 몬스터 이름 (Boss Monster Names per Act)
const Map<StoryAct, String> actBossNames = {
StoryAct.act1: 'BOSS: Stack Overflow Dragon',
StoryAct.act2: 'BOSS: Heap Corruption Hydra',
StoryAct.act3: 'BOSS: Kernel Panic Titan',
StoryAct.act4: 'BOSS: Zero Day Leviathan',
StoryAct.act5: 'BOSS: The Primordial Glitch',
};
/// Act별 시작 퀘스트 (Starting Quest per Act)
const Map<StoryAct, String> actStartingQuests = {
StoryAct.prologue: 'Exterminate the Bug Infestation',
StoryAct.act1: 'Purge the Bug Nest',
StoryAct.act2: 'Cleanse the Corrupted Network',
StoryAct.act3: 'Pass the Trials of the Ancient Compiler',
StoryAct.act4: "Infiltrate the Glitch God's Citadel",
StoryAct.act5: 'Defeat the Glitch God',
};
/// Act 제목 (Act Titles)
const Map<StoryAct, String> actTitles = {
StoryAct.prologue: 'Prologue',
StoryAct.act1: 'Act I: Awakening',
StoryAct.act2: 'Act II: Growth',
StoryAct.act3: 'Act III: Trials',
StoryAct.act4: 'Act IV: Confrontation',
StoryAct.act5: 'Act V: Endgame',
StoryAct.ending: 'The End',
};