feat(l10n): 장비/아이템 동적 이름 한국어 번역 지원

- pq_logic.dart: 구조화된 결과 타입 (EquipResult, ItemResult) 추가
- pq_logic.dart: 구조화된 생성 함수 (winEquipStructured, winItemStructured 등) 추가
- GameDataL10n: 구조화된 결과 렌더링 함수 추가 (renderEquipResult, renderItemResult)
- GameDataL10n: 문자열 파싱 기반 번역 함수 추가 (translateEquipString, translateItemString)
- game_play_screen.dart: 장비/아이템 목록에 번역 함수 적용
This commit is contained in:
JiWoong Sul
2025-12-11 18:36:51 +09:00
parent ebb0e0dda6
commit d4acd3503b
3 changed files with 411 additions and 13 deletions

View File

@@ -9,6 +9,70 @@ import 'package:askiineverdie/src/core/model/game_state.dart';
// Mirrors core utility functions from the original Delphi sources (Main.pas / NewGuy.pas).
/// 장비 생성 결과 (구조화된 데이터로 l10n 지원)
class EquipResult {
const EquipResult({
required this.baseName,
this.modifiers = const [],
this.plusValue = 0,
});
/// 기본 장비 이름 (예: "VPN Cloak")
final String baseName;
/// 수식어 목록 (예: ["Holey", "Deprecated"])
final List<String> modifiers;
/// +/- 수치 (예: -1, +2)
final int plusValue;
/// 영문 전체 이름 생성 (기존 방식)
String get displayName {
var name = baseName;
for (final mod in modifiers) {
name = '$mod $name';
}
if (plusValue != 0) {
name = '${plusValue > 0 ? '+' : ''}$plusValue $name';
}
return name;
}
}
/// 아이템 생성 결과 (구조화된 데이터로 l10n 지원)
class ItemResult {
const ItemResult({
this.attrib,
this.special,
this.itemOf,
this.boringItem,
});
/// 아이템 속성 (예: "Golden")
final String? attrib;
/// 특수 아이템 (예: "Iterator")
final String? special;
/// "~의" 접미사 (예: "Monitoring")
final String? itemOf;
/// 단순 아이템 (보링 아이템용)
final String? boringItem;
/// 영문 전체 이름 생성 (기존 방식)
String get displayName {
if (boringItem != null) return boringItem!;
if (attrib != null && special != null && itemOf != null) {
return '$attrib $special of $itemOf';
}
if (attrib != null && special != null) {
return '$attrib $special';
}
return '';
}
}
int levelUpTimeSeconds(int level) {
// ~20 minutes for level 1, then exponential growth (same as LevelUpTime in Main.pas).
final seconds = (20.0 + math.pow(1.15, level)) * 60.0;
@@ -101,6 +165,41 @@ String specialItem(PqConfig config, DeterministicRandom rng) {
return '${interestingItem(config, rng)} of ${pick(config.itemOfs, rng)}';
}
/// 구조화된 아이템 결과 반환 (l10n 지원)
ItemResult boringItemStructured(PqConfig config, DeterministicRandom rng) {
return ItemResult(boringItem: pick(config.boringItems, rng));
}
/// 구조화된 아이템 결과 반환 (l10n 지원)
ItemResult interestingItemStructured(PqConfig config, DeterministicRandom rng) {
return ItemResult(
attrib: pick(config.itemAttrib, rng),
special: pick(config.specials, rng),
);
}
/// 구조화된 아이템 결과 반환 (l10n 지원)
ItemResult specialItemStructured(PqConfig config, DeterministicRandom rng) {
return ItemResult(
attrib: pick(config.itemAttrib, rng),
special: pick(config.specials, rng),
itemOf: pick(config.itemOfs, rng),
);
}
/// 구조화된 아이템 결과 반환 (l10n 지원)
ItemResult winItemStructured(
PqConfig config,
DeterministicRandom rng,
int inventoryCount,
) {
final threshold = math.max(250, rng.nextInt(999));
if (inventoryCount > threshold) {
return const ItemResult(); // 빈 결과
}
return specialItemStructured(config, rng);
}
String pickWeapon(PqConfig config, DeterministicRandom rng, int level) {
return _lPick(config.weapons, rng, level);
}
@@ -256,6 +355,78 @@ String winEquipBySlot(
return winEquip(config, rng, level, slot.index);
}
/// 구조화된 장비 생성 결과 반환 (l10n 지원)
EquipResult winEquipStructured(
PqConfig config,
DeterministicRandom rng,
int level,
int slotIndex,
) {
final bool isWeapon = slotIndex == 0;
final List<String> items;
if (slotIndex == 0) {
items = config.weapons;
} else if (slotIndex == 1) {
items = config.shields;
} else {
items = config.armors;
}
final better = isWeapon ? config.offenseAttrib : config.defenseAttrib;
final worse = isWeapon ? config.offenseBad : config.defenseBad;
final base = _lPick(items, rng, level);
final parts = base.split('|');
final baseName = parts[0];
final qual = parts.length > 1
? int.tryParse(parts[1].replaceAll('+', '')) ?? 0
: 0;
final plus = level - qual;
final modifierList = plus >= 0 ? better : worse;
return _addModifierStructured(rng, baseName, modifierList, plus);
}
/// 구조화된 장비 결과 반환 (내부 함수)
EquipResult _addModifierStructured(
DeterministicRandom rng,
String baseName,
List<String> modifiers,
int plus,
) {
final collectedModifiers = <String>[];
var remaining = plus;
var count = 0;
while (count < 2 && remaining != 0) {
final modifier = pick(modifiers, rng);
final parts = modifier.split('|');
if (parts.isEmpty) break;
final label = parts[0];
final qual = parts.length > 1 ? int.tryParse(parts[1]) ?? 0 : 0;
if (collectedModifiers.contains(label)) break; // avoid repeats
if (remaining.abs() < qual.abs()) break;
collectedModifiers.add(label);
remaining -= qual;
count++;
}
return EquipResult(
baseName: baseName,
modifiers: collectedModifiers,
plusValue: remaining,
);
}
/// EquipmentSlot enum을 사용하는 구조화된 버전
EquipResult winEquipBySlotStructured(
PqConfig config,
DeterministicRandom rng,
int level,
EquipmentSlot slot,
) {
return winEquipStructured(config, rng, level, slot.index);
}
int winStatIndex(DeterministicRandom rng, List<int> statValues) {
// 원본 Main.pas:870-883
// 50%: 모든 8개 스탯 중 랜덤