refactor(util): pq_logic.dart 모듈 분할
- pq_random.dart: 랜덤/확률 함수 (61줄) - pq_string.dart: 문자열 유틸리티 (55줄) - pq_item.dart: 아이템/장비 생성 (327줄) - pq_monster.dart: 몬스터 생성 (283줄) - pq_quest.dart: 퀘스트/Act/시네마틱 (283줄) - pq_task.dart: 태스크/큐 (97줄) - pq_stat.dart: 스탯 관련 (64줄) - 원본은 re-export 허브로 유지 (역호환성)
This commit is contained in:
327
lib/src/core/util/pq_item.dart
Normal file
327
lib/src/core/util/pq_item.dart
Normal file
@@ -0,0 +1,327 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:asciineverdie/src/core/model/equipment_slot.dart';
|
||||
import 'package:asciineverdie/src/core/model/pq_config.dart';
|
||||
import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
||||
import 'package:asciineverdie/src/core/util/pq_random.dart';
|
||||
|
||||
/// 아이템/장비 생성 관련 함수들
|
||||
/// 원본 Main.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 '';
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 아이템 생성 함수들
|
||||
// =============================================================================
|
||||
|
||||
/// 단순 아이템 (Boring Item)
|
||||
String boringItem(PqConfig config, DeterministicRandom rng) {
|
||||
return pick(config.boringItems, rng);
|
||||
}
|
||||
|
||||
/// 흥미로운 아이템 (Interesting Item)
|
||||
String interestingItem(PqConfig config, DeterministicRandom rng) {
|
||||
final attr = pick(config.itemAttrib, rng);
|
||||
final special = pick(config.specials, rng);
|
||||
return '$attr $special';
|
||||
}
|
||||
|
||||
/// 특수 아이템 (Special Item)
|
||||
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 winItem(PqConfig config, DeterministicRandom rng, int inventoryCount) {
|
||||
final threshold = math.max(250, rng.nextInt(999));
|
||||
if (inventoryCount > threshold) return '';
|
||||
return specialItem(config, rng);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 장비 선택 함수들
|
||||
// =============================================================================
|
||||
|
||||
/// 무기 선택
|
||||
String pickWeapon(PqConfig config, DeterministicRandom rng, int level) {
|
||||
return _lPick(config.weapons, rng, level);
|
||||
}
|
||||
|
||||
/// 방패 선택
|
||||
String pickShield(PqConfig config, DeterministicRandom rng, int level) {
|
||||
return _lPick(config.shields, rng, level);
|
||||
}
|
||||
|
||||
/// 방어구 선택
|
||||
String pickArmor(PqConfig config, DeterministicRandom rng, int level) {
|
||||
return _lPick(config.armors, rng, level);
|
||||
}
|
||||
|
||||
/// 주문 선택
|
||||
String pickSpell(PqConfig config, DeterministicRandom rng, int goalLevel) {
|
||||
return _lPick(config.spells, rng, goalLevel);
|
||||
}
|
||||
|
||||
/// 원본 Main.pas:776-789 LPick: 6회 시도하여 목표 레벨에 가장 가까운 아이템 선택
|
||||
String _lPick(List<String> items, DeterministicRandom rng, int goal) {
|
||||
if (items.isEmpty) return '';
|
||||
var result = pick(items, rng);
|
||||
var bestLevel = _parseLevel(result);
|
||||
|
||||
for (var i = 0; i < 5; i++) {
|
||||
final candidate = pick(items, rng);
|
||||
final candLevel = _parseLevel(candidate);
|
||||
if ((goal - candLevel).abs() < (goal - bestLevel).abs()) {
|
||||
result = candidate;
|
||||
bestLevel = candLevel;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 아이템 문자열에서 레벨 파싱
|
||||
int _parseLevel(String entry) {
|
||||
final parts = entry.split('|');
|
||||
if (parts.length < 2) return 0;
|
||||
return int.tryParse(parts[1].replaceAll('+', '')) ?? 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 장비 생성 함수들
|
||||
// =============================================================================
|
||||
|
||||
/// 수식어 추가
|
||||
String addModifier(
|
||||
DeterministicRandom rng,
|
||||
String baseName,
|
||||
List<String> modifiers,
|
||||
int plus,
|
||||
) {
|
||||
var name = baseName;
|
||||
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 (name.contains(label)) break; // avoid repeats
|
||||
if (remaining.abs() < qual.abs()) break;
|
||||
name = '$label $name';
|
||||
remaining -= qual;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (remaining != 0) {
|
||||
name = '${remaining > 0 ? '+' : ''}$remaining $name';
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/// 장비 생성 (원본 Main.pas:791-830 WinEquip)
|
||||
/// [slotIndex]: 0=Weapon, 1=Shield, 2-10=Armor 계열
|
||||
String winEquip(
|
||||
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 addModifier(rng, baseName, modifierList, plus);
|
||||
}
|
||||
|
||||
/// EquipmentSlot enum을 사용하는 편의 함수
|
||||
String winEquipBySlot(
|
||||
PqConfig config,
|
||||
DeterministicRandom rng,
|
||||
int level,
|
||||
EquipmentSlot slot,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user