feat(l10n): 게임 텍스트 로컬라이제이션 확장
- game_text_l10n.dart: BuildContext 없이 사용할 수 있는 게임 텍스트 l10n 파일 생성 - progress_service.dart: 프롤로그/태스크 캡션 l10n 함수 사용으로 변경 - pq_logic.dart: 퀘스트/시네마틱/몬스터 수식어 l10n 함수 사용으로 변경 번역 적용 범위: - 프롤로그 텍스트 (4개) - 태스크 캡션 (컴파일, 이동, 디버깅, 판매 등) - 퀘스트 캡션 (패치, 찾기, 전송, 다운로드, 안정화) - 시네마틱 텍스트 (캐시 존, 전투, 배신 시나리오) - 몬스터 수식어 (sick, young, big, special 등 모든 수식어) - 시간 표시 (초, 분, 시간, 일) - impressiveGuy, namedMonster 패턴
This commit is contained in:
284
lib/data/game_text_l10n.dart
Normal file
284
lib/data/game_text_l10n.dart
Normal file
@@ -0,0 +1,284 @@
|
||||
// 게임 텍스트 로컬라이제이션 (BuildContext 없이 사용)
|
||||
// progress_service.dart, pq_logic.dart 등에서 사용
|
||||
|
||||
/// 현재 게임 로케일 설정 (전역)
|
||||
String _currentLocale = 'en';
|
||||
|
||||
/// 현재 로케일 가져오기
|
||||
String get currentGameLocale => _currentLocale;
|
||||
|
||||
/// 로케일 설정 (앱 시작 시 호출)
|
||||
void setGameLocale(String locale) {
|
||||
_currentLocale = locale;
|
||||
}
|
||||
|
||||
/// 한국어 여부 확인
|
||||
bool get isKoreanLocale => _currentLocale == 'ko';
|
||||
|
||||
// ============================================================================
|
||||
// 프롤로그 텍스트
|
||||
// ============================================================================
|
||||
|
||||
const _prologueTextsEn = [
|
||||
'Receiving an ominous vision from the Code God',
|
||||
'The old Compiler Sage reveals a prophecy: "The Glitch God has awakened"',
|
||||
'A sudden Buffer Overflow resets your village, leaving you as the sole survivor',
|
||||
'With unexpected resolve, you embark on a perilous journey to the Null Kingdom',
|
||||
];
|
||||
|
||||
const _prologueTextsKo = [
|
||||
'코드의 신으로부터 불길한 환영을 받다',
|
||||
'늙은 컴파일러 현자가 예언을 밝히다: "글리치 신이 깨어났다"',
|
||||
'갑작스러운 버퍼 오버플로우가 마을을 초기화하고, 당신만이 유일한 생존자로 남다',
|
||||
'예상치 못한 결의로 널(Null) 왕국을 향한 위험한 여정을 시작하다',
|
||||
];
|
||||
|
||||
List<String> get prologueTexts =>
|
||||
isKoreanLocale ? _prologueTextsKo : _prologueTextsEn;
|
||||
|
||||
// ============================================================================
|
||||
// 태스크 캡션
|
||||
// ============================================================================
|
||||
|
||||
String get taskCompiling => isKoreanLocale ? '컴파일 중' : 'Compiling';
|
||||
|
||||
String get taskPrologue => isKoreanLocale ? '프롤로그' : 'Prologue';
|
||||
|
||||
String taskHeadingToMarket() =>
|
||||
isKoreanLocale ? '전리품을 팔기 위해 데이터 마켓으로 이동 중' : 'Heading to the Data Market to trade loot';
|
||||
|
||||
String taskUpgradingHardware() =>
|
||||
isKoreanLocale ? '테크 샵에서 하드웨어 업그레이드 중' : 'Upgrading hardware at the Tech Shop';
|
||||
|
||||
String taskEnteringDebugZone() =>
|
||||
isKoreanLocale ? '디버그 존 진입 중' : 'Entering the Debug Zone';
|
||||
|
||||
String taskDebugging(String monsterName) =>
|
||||
isKoreanLocale ? '$monsterName 디버깅 중' : 'Debugging $monsterName';
|
||||
|
||||
String taskSelling(String itemDescription) =>
|
||||
isKoreanLocale ? '$itemDescription 판매 중' : 'Selling $itemDescription';
|
||||
|
||||
// ============================================================================
|
||||
// 퀘스트 캡션
|
||||
// ============================================================================
|
||||
|
||||
String questPatch(String name) =>
|
||||
isKoreanLocale ? '$name 패치하기' : 'Patch $name';
|
||||
|
||||
String questLocate(String item) =>
|
||||
isKoreanLocale ? '$item 찾기' : 'Locate $item';
|
||||
|
||||
String questTransfer(String item) =>
|
||||
isKoreanLocale ? '이 $item 전송하기' : 'Transfer this $item';
|
||||
|
||||
String questDownload(String item) =>
|
||||
isKoreanLocale ? '$item 다운로드하기' : 'Download $item';
|
||||
|
||||
String questStabilize(String name) =>
|
||||
isKoreanLocale ? '$name 안정화하기' : 'Stabilize $name';
|
||||
|
||||
// ============================================================================
|
||||
// Act 제목
|
||||
// ============================================================================
|
||||
|
||||
String actTitle(String romanNumeral) =>
|
||||
isKoreanLocale ? '$romanNumeral막' : 'Act $romanNumeral';
|
||||
|
||||
// ============================================================================
|
||||
// 시네마틱 텍스트 - 시나리오 1: 캐시 존
|
||||
// ============================================================================
|
||||
|
||||
String cinematicCacheZone1() => isKoreanLocale
|
||||
? '지쳐서 손상된 네트워크의 안전한 캐시 존에 도착하다'
|
||||
: 'Exhausted, you reach a safe Cache Zone in the corrupted network';
|
||||
|
||||
String cinematicCacheZone2() => isKoreanLocale
|
||||
? '옛 동맹들과 재연결하고 새로운 동료들을 포크하다'
|
||||
: 'You reconnect with old allies and fork new ones';
|
||||
|
||||
String cinematicCacheZone3() => isKoreanLocale
|
||||
? '디버거 기사단 회의에 참석하다'
|
||||
: 'You attend a council of the Debugger Knights';
|
||||
|
||||
String cinematicCacheZone4() => isKoreanLocale
|
||||
? '많은 버그들이 기다린다. 당신이 패치하도록 선택되었다!'
|
||||
: 'Many bugs await. You are chosen to patch them!';
|
||||
|
||||
// ============================================================================
|
||||
// 시네마틱 텍스트 - 시나리오 2: 전투
|
||||
// ============================================================================
|
||||
|
||||
String cinematicCombat1() => isKoreanLocale
|
||||
? '목표가 눈앞에 있지만, 치명적인 버그가 길을 막는다!'
|
||||
: 'Your target is in sight, but a critical bug blocks your path!';
|
||||
|
||||
String cinematicCombat2(String nemesis) => isKoreanLocale
|
||||
? '$nemesis와의 필사적인 디버깅 세션이 시작되다'
|
||||
: 'A desperate debugging session begins with $nemesis';
|
||||
|
||||
String cinematicCombatLocked(String nemesis) => isKoreanLocale
|
||||
? '$nemesis와 치열한 디버깅 중'
|
||||
: 'Locked in intense debugging with $nemesis';
|
||||
|
||||
String cinematicCombatCorrupts(String nemesis) => isKoreanLocale
|
||||
? '$nemesis가 당신의 스택 트레이스를 손상시키다'
|
||||
: '$nemesis corrupts your stack trace';
|
||||
|
||||
String cinematicCombatWorking(String nemesis) => isKoreanLocale
|
||||
? '당신의 패치가 $nemesis에게 효과를 보이는 것 같다'
|
||||
: 'Your patch seems to be working against $nemesis';
|
||||
|
||||
String cinematicCombatVictory(String nemesis) => isKoreanLocale
|
||||
? '승리! $nemesis가 패치되었다! 복구를 위해 시스템이 재부팅된다'
|
||||
: 'Victory! $nemesis is patched! System reboots for recovery';
|
||||
|
||||
String cinematicCombatWakeUp() => isKoreanLocale
|
||||
? '안전 모드에서 깨어나지만, 커널이 기다린다'
|
||||
: 'You wake up in a Safe Mode, but the kernel awaits';
|
||||
|
||||
// ============================================================================
|
||||
// 시네마틱 텍스트 - 시나리오 3: 배신
|
||||
// ============================================================================
|
||||
|
||||
String cinematicBetrayal1(String guy) => isKoreanLocale
|
||||
? '안도감! $guy의 보안 서버에 도착하다'
|
||||
: 'What relief! You reach the secure server of $guy';
|
||||
|
||||
String cinematicBetrayal2(String guy) => isKoreanLocale
|
||||
? '축하가 이어지고, $guy와 수상한 비밀 핸드셰이크를 나누다'
|
||||
: 'There is celebration, and a suspicious private handshake with $guy';
|
||||
|
||||
String cinematicBetrayal3(String item) => isKoreanLocale
|
||||
? '$item을 잊고 다시 가져오러 돌아가다'
|
||||
: 'You forget your $item and go back to retrieve it';
|
||||
|
||||
String cinematicBetrayal4() => isKoreanLocale
|
||||
? '이게 뭐지!? 손상된 패킷을 가로채다!'
|
||||
: 'What is this!? You intercept a corrupted packet!';
|
||||
|
||||
String cinematicBetrayal5(String guy) => isKoreanLocale
|
||||
? '$guy가 글리치 신의 백도어일 수 있을까?'
|
||||
: 'Could $guy be a backdoor for the Glitch God?';
|
||||
|
||||
String cinematicBetrayal6() => isKoreanLocale
|
||||
? '이 정보를 누구에게 맡길 수 있을까!? -- 바이너리 신전이다'
|
||||
: 'Who can be trusted with this intel!? -- The Binary Temple, of course';
|
||||
|
||||
// ============================================================================
|
||||
// 몬스터 수식어
|
||||
// ============================================================================
|
||||
|
||||
String modifierDead(String s) => isKoreanLocale ? '죽은 $s' : 'dead $s';
|
||||
String modifierComatose(String s) => isKoreanLocale ? '혼수상태의 $s' : 'comatose $s';
|
||||
String modifierCrippled(String s) => isKoreanLocale ? '불구의 $s' : 'crippled $s';
|
||||
String modifierSick(String s) => isKoreanLocale ? '병든 $s' : 'sick $s';
|
||||
String modifierUndernourished(String s) =>
|
||||
isKoreanLocale ? '영양실조 $s' : 'undernourished $s';
|
||||
|
||||
String modifierFoetal(String s) => isKoreanLocale ? '태아 $s' : 'foetal $s';
|
||||
String modifierBaby(String s) => isKoreanLocale ? '아기 $s' : 'baby $s';
|
||||
String modifierPreadolescent(String s) =>
|
||||
isKoreanLocale ? '사춘기 전 $s' : 'preadolescent $s';
|
||||
String modifierTeenage(String s) => isKoreanLocale ? '십대 $s' : 'teenage $s';
|
||||
String modifierUnderage(String s) => isKoreanLocale ? '미성년 $s' : 'underage $s';
|
||||
|
||||
String modifierGreater(String s) => isKoreanLocale ? '상위 $s' : 'greater $s';
|
||||
String modifierMassive(String s) => isKoreanLocale ? '거대한 $s' : 'massive $s';
|
||||
String modifierEnormous(String s) => isKoreanLocale ? '초거대 $s' : 'enormous $s';
|
||||
String modifierGiant(String s) => isKoreanLocale ? '자이언트 $s' : 'giant $s';
|
||||
String modifierTitanic(String s) => isKoreanLocale ? '타이타닉 $s' : 'titanic $s';
|
||||
|
||||
String modifierVeteran(String s) => isKoreanLocale ? '베테랑 $s' : 'veteran $s';
|
||||
String modifierBattle(String s) => isKoreanLocale ? '전투-$s' : 'Battle-$s';
|
||||
String modifierCursed(String s) => isKoreanLocale ? '저주받은 $s' : 'cursed $s';
|
||||
String modifierWarrior(String s) => isKoreanLocale ? '전사 $s' : 'warrior $s';
|
||||
String modifierWere(String s) => isKoreanLocale ? '늑대인간-$s' : 'Were-$s';
|
||||
String modifierUndead(String s) => isKoreanLocale ? '언데드 $s' : 'undead $s';
|
||||
String modifierDemon(String s) => isKoreanLocale ? '데몬 $s' : 'demon $s';
|
||||
|
||||
String modifierMessianic(String s) => isKoreanLocale ? '메시아닉 $s' : 'messianic $s';
|
||||
String modifierImaginary(String s) => isKoreanLocale ? '상상의 $s' : 'imaginary $s';
|
||||
String modifierPassing(String s) => isKoreanLocale ? '지나가는 $s' : 'passing $s';
|
||||
|
||||
// ============================================================================
|
||||
// 시간 표시
|
||||
// ============================================================================
|
||||
|
||||
String roughTimeSeconds(int seconds) =>
|
||||
isKoreanLocale ? '$seconds초' : '$seconds seconds';
|
||||
|
||||
String roughTimeMinutes(int minutes) =>
|
||||
isKoreanLocale ? '$minutes분' : '$minutes minutes';
|
||||
|
||||
String roughTimeHours(int hours) =>
|
||||
isKoreanLocale ? '$hours시간' : '$hours hours';
|
||||
|
||||
String roughTimeDays(int days) =>
|
||||
isKoreanLocale ? '$days일' : '$days days';
|
||||
|
||||
// ============================================================================
|
||||
// 영어 문법 함수 (한국어에서는 단순화)
|
||||
// ============================================================================
|
||||
|
||||
/// 관사 + 명사 (한국어: 수량만 표시)
|
||||
String indefiniteL10n(String s, int qty) {
|
||||
if (isKoreanLocale) {
|
||||
return qty == 1 ? s : '$qty $s';
|
||||
}
|
||||
// 영어 로직
|
||||
if (qty == 1) {
|
||||
const vowels = 'AEIOUÜaeiouü';
|
||||
final first = s.isNotEmpty ? s[0] : 'a';
|
||||
final article = vowels.contains(first) ? 'an' : 'a';
|
||||
return '$article $s';
|
||||
}
|
||||
return '$qty ${_pluralize(s)}';
|
||||
}
|
||||
|
||||
/// the + 명사 (한국어: 그냥 명사)
|
||||
String definiteL10n(String s, int qty) {
|
||||
if (isKoreanLocale) {
|
||||
return s;
|
||||
}
|
||||
// 영어 로직
|
||||
if (qty > 1) {
|
||||
s = _pluralize(s);
|
||||
}
|
||||
return 'the $s';
|
||||
}
|
||||
|
||||
/// 복수형 (영어만 해당)
|
||||
String _pluralize(String s) {
|
||||
if (s.endsWith('y')) return '${s.substring(0, s.length - 1)}ies';
|
||||
if (s.endsWith('us')) return '${s.substring(0, s.length - 2)}i';
|
||||
if (s.endsWith('ch') || s.endsWith('x') || s.endsWith('s')) return '${s}es';
|
||||
if (s.endsWith('f')) return '${s.substring(0, s.length - 1)}ves';
|
||||
if (s.endsWith('man') || s.endsWith('Man')) {
|
||||
return '${s.substring(0, s.length - 2)}en';
|
||||
}
|
||||
return '${s}s'; // ignore: unnecessary_brace_in_string_interp
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// impressiveGuy 관련
|
||||
// ============================================================================
|
||||
|
||||
String impressiveGuyPattern1(String title, String race) => isKoreanLocale
|
||||
// ignore: unnecessary_brace_in_string_interps
|
||||
? '${race}들의 $title' // 한국어 조사 연결을 위해 중괄호 필요
|
||||
: 'the $title of the ${_pluralize(race)}';
|
||||
|
||||
String impressiveGuyPattern2(String title, String name1, String name2) =>
|
||||
isKoreanLocale
|
||||
? '$name2의 $title $name1'
|
||||
: '$title $name1 of $name2';
|
||||
|
||||
// ============================================================================
|
||||
// namedMonster 관련
|
||||
// ============================================================================
|
||||
|
||||
String namedMonsterFormat(String generatedName, String monsterType) =>
|
||||
isKoreanLocale
|
||||
? '$monsterType $generatedName'
|
||||
: '$generatedName the $monsterType';
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
|
||||
import 'package:askiineverdie/src/core/engine/reward_service.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
@@ -37,48 +38,47 @@ class ProgressService {
|
||||
/// 새 게임 초기화 (원본 GoButtonClick, Main.pas:741-767)
|
||||
/// Prologue 태스크들을 큐에 추가하고 첫 태스크 시작
|
||||
GameState initializeNewGame(GameState state) {
|
||||
// 초기 큐 설정 - 아스키나라(ASCII-Nara) 세계관 프롤로그
|
||||
// 초기 큐 설정 - 아스키나라(ASCII-Nara) 세계관 프롤로그 (l10n 지원)
|
||||
final prologueTexts = l10n.prologueTexts;
|
||||
final initialQueue = <QueueEntry>[
|
||||
const QueueEntry(
|
||||
QueueEntry(
|
||||
kind: QueueKind.task,
|
||||
durationMillis: 10 * 1000,
|
||||
caption: 'Receiving an ominous vision from the Code God',
|
||||
caption: prologueTexts[0],
|
||||
taskType: TaskType.load,
|
||||
),
|
||||
const QueueEntry(
|
||||
QueueEntry(
|
||||
kind: QueueKind.task,
|
||||
durationMillis: 6 * 1000,
|
||||
caption:
|
||||
'The old Compiler Sage reveals a prophecy: '
|
||||
'"The Glitch God has awakened"',
|
||||
caption: prologueTexts[1],
|
||||
taskType: TaskType.load,
|
||||
),
|
||||
const QueueEntry(
|
||||
QueueEntry(
|
||||
kind: QueueKind.task,
|
||||
durationMillis: 6 * 1000,
|
||||
caption:
|
||||
'A sudden Buffer Overflow resets your village, '
|
||||
'leaving you as the sole survivor',
|
||||
caption: prologueTexts[2],
|
||||
taskType: TaskType.load,
|
||||
),
|
||||
const QueueEntry(
|
||||
QueueEntry(
|
||||
kind: QueueKind.task,
|
||||
durationMillis: 4 * 1000,
|
||||
caption:
|
||||
'With unexpected resolve, you embark on a perilous journey '
|
||||
'to the Null Kingdom',
|
||||
caption: prologueTexts[3],
|
||||
taskType: TaskType.load,
|
||||
),
|
||||
const QueueEntry(
|
||||
QueueEntry(
|
||||
kind: QueueKind.plot,
|
||||
durationMillis: 2 * 1000,
|
||||
caption: 'Compiling',
|
||||
caption: l10n.taskCompiling,
|
||||
taskType: TaskType.plot,
|
||||
),
|
||||
];
|
||||
|
||||
// 첫 번째 태스크 시작 (원본 752줄)
|
||||
final taskResult = pq_logic.startTask(state.progress, 'Compiling', 2 * 1000);
|
||||
final taskResult = pq_logic.startTask(
|
||||
state.progress,
|
||||
l10n.taskCompiling,
|
||||
2 * 1000,
|
||||
);
|
||||
|
||||
// ExpBar 초기화 (원본 743-746줄)
|
||||
final expBar = ProgressBarState(position: 0, max: pq_logic.levelUpTime(1));
|
||||
@@ -89,10 +89,13 @@ class ProgressService {
|
||||
final progress = taskResult.progress.copyWith(
|
||||
exp: expBar,
|
||||
plot: plotBar,
|
||||
currentTask: const TaskInfo(caption: 'Compiling...', type: TaskType.load),
|
||||
currentTask: TaskInfo(
|
||||
caption: '${l10n.taskCompiling}...',
|
||||
type: TaskType.load,
|
||||
),
|
||||
plotStageCount: 1, // Prologue
|
||||
questCount: 0,
|
||||
plotHistory: const [HistoryEntry(caption: 'Prologue', isComplete: false)],
|
||||
plotHistory: [HistoryEntry(caption: l10n.taskPrologue, isComplete: false)],
|
||||
questHistory: const [],
|
||||
);
|
||||
|
||||
@@ -295,7 +298,7 @@ class ProgressService {
|
||||
progress.encumbrance.max > 0) {
|
||||
final taskResult = pq_logic.startTask(
|
||||
progress,
|
||||
'Heading to the Data Market to trade loot',
|
||||
l10n.taskHeadingToMarket(),
|
||||
4 * 1000,
|
||||
);
|
||||
progress = taskResult.progress.copyWith(
|
||||
@@ -316,7 +319,7 @@ class ProgressService {
|
||||
if (gold > equipPrice) {
|
||||
final taskResult = pq_logic.startTask(
|
||||
progress,
|
||||
'Upgrading hardware at the Tech Shop',
|
||||
l10n.taskUpgradingHardware(),
|
||||
5 * 1000,
|
||||
);
|
||||
progress = taskResult.progress.copyWith(
|
||||
@@ -331,7 +334,7 @@ class ProgressService {
|
||||
// Gold가 부족하면 전장으로 이동 (원본 674-676줄)
|
||||
final taskResult = pq_logic.startTask(
|
||||
progress,
|
||||
'Entering the Debug Zone',
|
||||
l10n.taskEnteringDebugZone(),
|
||||
4 * 1000,
|
||||
);
|
||||
progress = taskResult.progress.copyWith(
|
||||
@@ -370,7 +373,7 @@ class ProgressService {
|
||||
|
||||
final taskResult = pq_logic.startTask(
|
||||
progress,
|
||||
'Debugging ${monsterResult.displayName}',
|
||||
l10n.taskDebugging(monsterResult.displayName),
|
||||
durationMillis,
|
||||
);
|
||||
|
||||
@@ -711,9 +714,10 @@ class ProgressService {
|
||||
if (hasItemsToSell) {
|
||||
// 다음 아이템 판매 태스크 시작
|
||||
final nextItem = items.first;
|
||||
final itemDesc = l10n.indefiniteL10n(nextItem.name, nextItem.count);
|
||||
final taskResult = pq_logic.startTask(
|
||||
state.progress,
|
||||
'Selling ${pq_logic.indefinite(nextItem.name, nextItem.count)}',
|
||||
l10n.taskSelling(itemDesc),
|
||||
1 * 1000,
|
||||
);
|
||||
final progress = taskResult.progress.copyWith(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
import 'package:askiineverdie/src/core/model/pq_config.dart';
|
||||
import 'package:askiineverdie/src/core/util/roman.dart';
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/pq_config.dart';
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
import 'package:askiineverdie/src/core/util/roman.dart';
|
||||
|
||||
// Mirrors core utility functions from the original Delphi sources (Main.pas / NewGuy.pas).
|
||||
|
||||
@@ -80,15 +81,16 @@ int levelUpTimeSeconds(int level) {
|
||||
}
|
||||
|
||||
/// 초 단위 시간을 사람이 읽기 쉬운 형태로 변환 (원본 Main.pas:1265-1271)
|
||||
/// l10n 지원
|
||||
String roughTime(int seconds) {
|
||||
if (seconds < 120) {
|
||||
return '$seconds seconds';
|
||||
return l10n.roughTimeSeconds(seconds);
|
||||
} else if (seconds < 60 * 120) {
|
||||
return '${seconds ~/ 60} minutes';
|
||||
return l10n.roughTimeMinutes(seconds ~/ 60);
|
||||
} else if (seconds < 60 * 60 * 48) {
|
||||
return '${seconds ~/ 3600} hours';
|
||||
return l10n.roughTimeHours(seconds ~/ 3600);
|
||||
} else {
|
||||
return '${seconds ~/ (3600 * 24)} days';
|
||||
return l10n.roughTimeDays(seconds ~/ (3600 * 24));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +514,7 @@ MonsterTaskResult monsterTask(
|
||||
if (rng.nextInt(2) == 0) {
|
||||
// 'passing Race Class' 형태
|
||||
final klass = pick(config.klasses, rng).split('|').first;
|
||||
monster = 'passing $race $klass';
|
||||
monster = l10n.modifierPassing('$race $klass');
|
||||
} else {
|
||||
// 'Title Name the Race' 형태 (원본은 PickLow(Titles) 사용)
|
||||
final title = pickLow(config.titles, rng);
|
||||
@@ -562,7 +564,7 @@ MonsterTaskResult monsterTask(
|
||||
}
|
||||
|
||||
if (levelDiff <= -10) {
|
||||
name = 'imaginary $name';
|
||||
name = l10n.modifierImaginary(name);
|
||||
} else if (levelDiff < -5) {
|
||||
final i = 5 - rng.nextInt(10 + levelDiff + 1);
|
||||
name = _sick(i, _young((monsterLevel - targetLevel) - i, name));
|
||||
@@ -573,7 +575,7 @@ MonsterTaskResult monsterTask(
|
||||
name = _young(levelDiff, name);
|
||||
}
|
||||
} else if (levelDiff >= 10) {
|
||||
name = 'messianic $name';
|
||||
name = l10n.modifierMessianic(name);
|
||||
} else if (levelDiff > 5) {
|
||||
final i = 5 - rng.nextInt(10 - levelDiff + 1);
|
||||
name = _big(i, _special((levelDiff) - i, name));
|
||||
@@ -669,7 +671,7 @@ QuestResult completeQuest(PqConfig config, DeterministicRandom rng, int level) {
|
||||
}
|
||||
final name = best.split('|').first;
|
||||
return QuestResult(
|
||||
caption: 'Patch ${definite(name, 2)}',
|
||||
caption: l10n.questPatch(l10n.definiteL10n(name, 2)),
|
||||
reward: reward,
|
||||
monsterName: best,
|
||||
monsterLevel: bestLevel,
|
||||
@@ -677,14 +679,20 @@ QuestResult completeQuest(PqConfig config, DeterministicRandom rng, int level) {
|
||||
);
|
||||
case 1:
|
||||
final item = interestingItem(config, rng);
|
||||
return QuestResult(caption: 'Locate ${definite(item, 1)}', reward: reward);
|
||||
return QuestResult(
|
||||
caption: l10n.questLocate(l10n.definiteL10n(item, 1)),
|
||||
reward: reward,
|
||||
);
|
||||
case 2:
|
||||
final item = boringItem(config, rng);
|
||||
return QuestResult(caption: 'Transfer this $item', reward: reward);
|
||||
return QuestResult(
|
||||
caption: l10n.questTransfer(item),
|
||||
reward: reward,
|
||||
);
|
||||
case 3:
|
||||
final item = boringItem(config, rng);
|
||||
return QuestResult(
|
||||
caption: 'Download ${indefinite(item, 1)}',
|
||||
caption: l10n.questDownload(l10n.indefiniteL10n(item, 1)),
|
||||
reward: reward,
|
||||
);
|
||||
default:
|
||||
@@ -703,7 +711,7 @@ QuestResult completeQuest(PqConfig config, DeterministicRandom rng, int level) {
|
||||
final name = best.split('|').first;
|
||||
// Stabilize는 fQuest.Caption := '' 로 비움 → monsterIndex 미저장
|
||||
return QuestResult(
|
||||
caption: 'Stabilize ${definite(name, 2)}',
|
||||
caption: l10n.questStabilize(l10n.definiteL10n(name, 2)),
|
||||
reward: reward,
|
||||
);
|
||||
}
|
||||
@@ -723,7 +731,7 @@ class ActResult {
|
||||
|
||||
ActResult completeAct(int existingActCount) {
|
||||
final nextActIndex = existingActCount;
|
||||
final title = 'Act ${intToRoman(nextActIndex)}';
|
||||
final title = l10n.actTitle(intToRoman(nextActIndex));
|
||||
final plotBarMax = 60 * 60 * (1 + 5 * existingActCount);
|
||||
|
||||
final rewards = <RewardKind>[];
|
||||
@@ -815,19 +823,19 @@ String _sick(int m, String s) {
|
||||
switch (m) {
|
||||
case -5:
|
||||
case 5:
|
||||
return 'dead $s';
|
||||
return l10n.modifierDead(s);
|
||||
case -4:
|
||||
case 4:
|
||||
return 'comatose $s';
|
||||
return l10n.modifierComatose(s);
|
||||
case -3:
|
||||
case 3:
|
||||
return 'crippled $s';
|
||||
return l10n.modifierCrippled(s);
|
||||
case -2:
|
||||
case 2:
|
||||
return 'sick $s';
|
||||
return l10n.modifierSick(s);
|
||||
case -1:
|
||||
case 1:
|
||||
return 'undernourished $s';
|
||||
return l10n.modifierUndernourished(s);
|
||||
default:
|
||||
return '$m$s';
|
||||
}
|
||||
@@ -837,19 +845,19 @@ String _young(int m, String s) {
|
||||
switch (-m) {
|
||||
case -5:
|
||||
case 5:
|
||||
return 'foetal $s';
|
||||
return l10n.modifierFoetal(s);
|
||||
case -4:
|
||||
case 4:
|
||||
return 'baby $s';
|
||||
return l10n.modifierBaby(s);
|
||||
case -3:
|
||||
case 3:
|
||||
return 'preadolescent $s';
|
||||
return l10n.modifierPreadolescent(s);
|
||||
case -2:
|
||||
case 2:
|
||||
return 'teenage $s';
|
||||
return l10n.modifierTeenage(s);
|
||||
case -1:
|
||||
case 1:
|
||||
return 'underage $s';
|
||||
return l10n.modifierUnderage(s);
|
||||
default:
|
||||
return '$m$s';
|
||||
}
|
||||
@@ -859,19 +867,19 @@ String _big(int m, String s) {
|
||||
switch (m) {
|
||||
case 1:
|
||||
case -1:
|
||||
return 'greater $s';
|
||||
return l10n.modifierGreater(s);
|
||||
case 2:
|
||||
case -2:
|
||||
return 'massive $s';
|
||||
return l10n.modifierMassive(s);
|
||||
case 3:
|
||||
case -3:
|
||||
return 'enormous $s';
|
||||
return l10n.modifierEnormous(s);
|
||||
case 4:
|
||||
case -4:
|
||||
return 'giant $s';
|
||||
return l10n.modifierGiant(s);
|
||||
case 5:
|
||||
case -5:
|
||||
return 'titanic $s';
|
||||
return l10n.modifierTitanic(s);
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
@@ -881,19 +889,19 @@ String _special(int m, String s) {
|
||||
switch (-m) {
|
||||
case 1:
|
||||
case -1:
|
||||
return s.contains(' ') ? 'veteran $s' : 'Battle-$s';
|
||||
return s.contains(' ') ? l10n.modifierVeteran(s) : l10n.modifierBattle(s);
|
||||
case 2:
|
||||
case -2:
|
||||
return 'cursed $s';
|
||||
return l10n.modifierCursed(s);
|
||||
case 3:
|
||||
case -3:
|
||||
return s.contains(' ') ? 'warrior $s' : 'Were-$s';
|
||||
return s.contains(' ') ? l10n.modifierWarrior(s) : l10n.modifierWere(s);
|
||||
case 4:
|
||||
case -4:
|
||||
return 'undead $s';
|
||||
return l10n.modifierUndead(s);
|
||||
case 5:
|
||||
case -5:
|
||||
return 'demon $s';
|
||||
return l10n.modifierDemon(s);
|
||||
default:
|
||||
return s;
|
||||
}
|
||||
@@ -916,25 +924,26 @@ String _pick(String pipeSeparated, DeterministicRandom rng) {
|
||||
// =============================================================================
|
||||
|
||||
/// NPC 이름 생성 (ImpressiveGuy, Main.pas:514-521)
|
||||
/// 인상적인 타이틀 + 종족 또는 이름 조합
|
||||
/// 인상적인 타이틀 + 종족 또는 이름 조합 (l10n 지원)
|
||||
String impressiveGuy(PqConfig config, DeterministicRandom rng) {
|
||||
var result = pick(config.impressiveTitles, rng);
|
||||
final title = pick(config.impressiveTitles, rng);
|
||||
switch (rng.nextInt(2)) {
|
||||
case 0:
|
||||
// "the King of the Elves" 형태
|
||||
// "the King of the Elves" / "엘프들의 왕" 형태
|
||||
final race = pick(config.races, rng).split('|').first;
|
||||
result = 'the $result of the ${pluralize(race)}';
|
||||
break;
|
||||
return l10n.impressiveGuyPattern1(title, race);
|
||||
case 1:
|
||||
// "King Vrognak of Zoxzik" 형태
|
||||
result = '$result ${generateName(rng)} of ${generateName(rng)}';
|
||||
break;
|
||||
// "King Vrognak of Zoxzik" / "Zoxzik의 왕 Vrognak" 형태
|
||||
final name1 = generateName(rng);
|
||||
final name2 = generateName(rng);
|
||||
return l10n.impressiveGuyPattern2(title, name1, name2);
|
||||
default:
|
||||
return title;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 이름 있는 몬스터 생성 (NamedMonster, Main.pas:498-512)
|
||||
/// 레벨에 맞는 몬스터 선택 후 GenerateName으로 이름 붙이기
|
||||
/// 레벨에 맞는 몬스터 선택 후 GenerateName으로 이름 붙이기 (l10n 지원)
|
||||
String namedMonster(PqConfig config, DeterministicRandom rng, int level) {
|
||||
String best = '';
|
||||
int bestLevel = 0;
|
||||
@@ -952,11 +961,12 @@ String namedMonster(PqConfig config, DeterministicRandom rng, int level) {
|
||||
}
|
||||
}
|
||||
|
||||
return '${generateName(rng)} the $best';
|
||||
// "GeneratedName the MonsterType" / "몬스터타입 GeneratedName" 형태
|
||||
return l10n.namedMonsterFormat(generateName(rng), best);
|
||||
}
|
||||
|
||||
/// 플롯 간 시네마틱 이벤트 생성 (InterplotCinematic, Main.pas:456-495)
|
||||
/// 3가지 시나리오 중 하나를 랜덤 선택
|
||||
/// 3가지 시나리오 중 하나를 랜덤 선택 (l10n 지원)
|
||||
List<QueueEntry> interplotCinematic(
|
||||
PqConfig config,
|
||||
DeterministicRandom rng,
|
||||
@@ -975,25 +985,17 @@ List<QueueEntry> interplotCinematic(
|
||||
switch (rng.nextInt(3)) {
|
||||
case 0:
|
||||
// 시나리오 1: 안전한 캐시 영역 도착
|
||||
q(
|
||||
QueueKind.task,
|
||||
1,
|
||||
'Exhausted, you reach a safe Cache Zone in the corrupted network',
|
||||
);
|
||||
q(QueueKind.task, 2, 'You reconnect with old allies and fork new ones');
|
||||
q(QueueKind.task, 2, 'You attend a council of the Debugger Knights');
|
||||
q(QueueKind.task, 1, 'Many bugs await. You are chosen to patch them!');
|
||||
q(QueueKind.task, 1, l10n.cinematicCacheZone1());
|
||||
q(QueueKind.task, 2, l10n.cinematicCacheZone2());
|
||||
q(QueueKind.task, 2, l10n.cinematicCacheZone3());
|
||||
q(QueueKind.task, 1, l10n.cinematicCacheZone4());
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// 시나리오 2: 강력한 버그와의 전투
|
||||
q(
|
||||
QueueKind.task,
|
||||
1,
|
||||
'Your target is in sight, but a critical bug blocks your path!',
|
||||
);
|
||||
q(QueueKind.task, 1, l10n.cinematicCombat1());
|
||||
final nemesis = namedMonster(config, rng, level + 3);
|
||||
q(QueueKind.task, 4, 'A desperate debugging session begins with $nemesis');
|
||||
q(QueueKind.task, 4, l10n.cinematicCombat2(nemesis));
|
||||
|
||||
var s = rng.nextInt(3);
|
||||
final combatRounds = rng.nextInt(1 + plotCount);
|
||||
@@ -1001,63 +1003,35 @@ List<QueueEntry> interplotCinematic(
|
||||
s += 1 + rng.nextInt(2);
|
||||
switch (s % 3) {
|
||||
case 0:
|
||||
q(QueueKind.task, 2, 'Locked in intense debugging with $nemesis');
|
||||
q(QueueKind.task, 2, l10n.cinematicCombatLocked(nemesis));
|
||||
break;
|
||||
case 1:
|
||||
q(QueueKind.task, 2, '$nemesis corrupts your stack trace');
|
||||
q(QueueKind.task, 2, l10n.cinematicCombatCorrupts(nemesis));
|
||||
break;
|
||||
case 2:
|
||||
q(
|
||||
QueueKind.task,
|
||||
2,
|
||||
'Your patch seems to be working against $nemesis',
|
||||
);
|
||||
q(QueueKind.task, 2, l10n.cinematicCombatWorking(nemesis));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
q(
|
||||
QueueKind.task,
|
||||
3,
|
||||
'Victory! $nemesis is patched! System reboots for recovery',
|
||||
);
|
||||
q(
|
||||
QueueKind.task,
|
||||
2,
|
||||
'You wake up in a Safe Mode, but the kernel awaits',
|
||||
);
|
||||
q(QueueKind.task, 3, l10n.cinematicCombatVictory(nemesis));
|
||||
q(QueueKind.task, 2, l10n.cinematicCombatWakeUp());
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 시나리오 3: 내부자 위협 발견
|
||||
final guy = impressiveGuy(config, rng);
|
||||
q(
|
||||
QueueKind.task,
|
||||
2,
|
||||
'What relief! You reach the secure server of $guy',
|
||||
);
|
||||
q(
|
||||
QueueKind.task,
|
||||
3,
|
||||
'There is celebration, and a suspicious private handshake with $guy',
|
||||
);
|
||||
q(
|
||||
QueueKind.task,
|
||||
2,
|
||||
'You forget your ${boringItem(config, rng)} and go back to retrieve it',
|
||||
);
|
||||
q(QueueKind.task, 2, 'What is this!? You intercept a corrupted packet!');
|
||||
q(QueueKind.task, 2, 'Could $guy be a backdoor for the Glitch God?');
|
||||
q(
|
||||
QueueKind.task,
|
||||
3,
|
||||
'Who can be trusted with this intel!? -- The Binary Temple, of course',
|
||||
);
|
||||
q(QueueKind.task, 2, l10n.cinematicBetrayal1(guy));
|
||||
q(QueueKind.task, 3, l10n.cinematicBetrayal2(guy));
|
||||
q(QueueKind.task, 2, l10n.cinematicBetrayal3(boringItem(config, rng)));
|
||||
q(QueueKind.task, 2, l10n.cinematicBetrayal4());
|
||||
q(QueueKind.task, 2, l10n.cinematicBetrayal5(guy));
|
||||
q(QueueKind.task, 3, l10n.cinematicBetrayal6());
|
||||
break;
|
||||
}
|
||||
|
||||
// 마지막에 plot 추가
|
||||
q(QueueKind.plot, 2, 'Compiling');
|
||||
q(QueueKind.plot, 2, l10n.taskCompiling);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user