- 기본 무기를 'Sharp Stick'에서 'Keyboard'로 변경 (아스키나라 세계관) - 몬스터 번역 168개 추가 (보안 위협, 버그 등) - BoringItems(잡템) 번역 42개 추가 - game_data_l10n에서 boringItem 번역 적용
564 lines
14 KiB
Dart
564 lines
14 KiB
Dart
import 'dart:collection';
|
|
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
|
|
|
/// Minimal skeletal state to mirror Progress Quest structures.
|
|
///
|
|
/// Logic will be ported faithfully from the Delphi source; this file only
|
|
/// defines containers and helpers for deterministic RNG.
|
|
class GameState {
|
|
GameState({
|
|
required DeterministicRandom rng,
|
|
Traits? traits,
|
|
Stats? stats,
|
|
Inventory? inventory,
|
|
Equipment? equipment,
|
|
SpellBook? spellBook,
|
|
ProgressState? progress,
|
|
QueueState? queue,
|
|
}) : rng = DeterministicRandom.clone(rng),
|
|
traits = traits ?? Traits.empty(),
|
|
stats = stats ?? Stats.empty(),
|
|
inventory = inventory ?? Inventory.empty(),
|
|
equipment = equipment ?? Equipment.empty(),
|
|
spellBook = spellBook ?? SpellBook.empty(),
|
|
progress = progress ?? ProgressState.empty(),
|
|
queue = queue ?? QueueState.empty();
|
|
|
|
factory GameState.withSeed({
|
|
required int seed,
|
|
Traits? traits,
|
|
Stats? stats,
|
|
Inventory? inventory,
|
|
Equipment? equipment,
|
|
SpellBook? spellBook,
|
|
ProgressState? progress,
|
|
QueueState? queue,
|
|
}) {
|
|
return GameState(
|
|
rng: DeterministicRandom(seed),
|
|
traits: traits,
|
|
stats: stats,
|
|
inventory: inventory,
|
|
equipment: equipment,
|
|
spellBook: spellBook,
|
|
progress: progress,
|
|
queue: queue,
|
|
);
|
|
}
|
|
|
|
final DeterministicRandom rng;
|
|
final Traits traits;
|
|
final Stats stats;
|
|
final Inventory inventory;
|
|
final Equipment equipment;
|
|
final SpellBook spellBook;
|
|
final ProgressState progress;
|
|
final QueueState queue;
|
|
|
|
GameState copyWith({
|
|
DeterministicRandom? rng,
|
|
Traits? traits,
|
|
Stats? stats,
|
|
Inventory? inventory,
|
|
Equipment? equipment,
|
|
SpellBook? spellBook,
|
|
ProgressState? progress,
|
|
QueueState? queue,
|
|
}) {
|
|
return GameState(
|
|
rng: rng ?? DeterministicRandom.clone(this.rng),
|
|
traits: traits ?? this.traits,
|
|
stats: stats ?? this.stats,
|
|
inventory: inventory ?? this.inventory,
|
|
equipment: equipment ?? this.equipment,
|
|
spellBook: spellBook ?? this.spellBook,
|
|
progress: progress ?? this.progress,
|
|
queue: queue ?? this.queue,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 태스크 타입 (원본 fTask.Caption 값들에 대응)
|
|
enum TaskType {
|
|
neutral, // heading 등 일반 이동
|
|
kill, // 몬스터 처치
|
|
load, // 로딩/초기화
|
|
plot, // 플롯 진행
|
|
market, // 시장으로 이동 중
|
|
sell, // 아이템 판매 중
|
|
buying, // 장비 구매 중
|
|
}
|
|
|
|
class TaskInfo {
|
|
const TaskInfo({
|
|
required this.caption,
|
|
required this.type,
|
|
this.monsterBaseName,
|
|
this.monsterPart,
|
|
});
|
|
|
|
final String caption;
|
|
final TaskType type;
|
|
|
|
/// 킬 태스크의 몬스터 기본 이름 (형용사 제외, 예: "Goblin")
|
|
final String? monsterBaseName;
|
|
|
|
/// 킬 태스크의 전리품 부위 (예: "claw", "tail", "*"는 WinItem)
|
|
final String? monsterPart;
|
|
|
|
factory TaskInfo.empty() =>
|
|
const TaskInfo(caption: '', type: TaskType.neutral);
|
|
|
|
TaskInfo copyWith({
|
|
String? caption,
|
|
TaskType? type,
|
|
String? monsterBaseName,
|
|
String? monsterPart,
|
|
}) {
|
|
return TaskInfo(
|
|
caption: caption ?? this.caption,
|
|
type: type ?? this.type,
|
|
monsterBaseName: monsterBaseName ?? this.monsterBaseName,
|
|
monsterPart: monsterPart ?? this.monsterPart,
|
|
);
|
|
}
|
|
}
|
|
|
|
class Traits {
|
|
const Traits({
|
|
required this.name,
|
|
required this.race,
|
|
required this.klass,
|
|
required this.level,
|
|
required this.motto,
|
|
required this.guild,
|
|
});
|
|
|
|
final String name;
|
|
final String race;
|
|
final String klass;
|
|
final int level;
|
|
final String motto;
|
|
final String guild;
|
|
|
|
factory Traits.empty() => const Traits(
|
|
name: '',
|
|
race: '',
|
|
klass: '',
|
|
level: 1,
|
|
motto: '',
|
|
guild: '',
|
|
);
|
|
|
|
Traits copyWith({
|
|
String? name,
|
|
String? race,
|
|
String? klass,
|
|
int? level,
|
|
String? motto,
|
|
String? guild,
|
|
}) {
|
|
return Traits(
|
|
name: name ?? this.name,
|
|
race: race ?? this.race,
|
|
klass: klass ?? this.klass,
|
|
level: level ?? this.level,
|
|
motto: motto ?? this.motto,
|
|
guild: guild ?? this.guild,
|
|
);
|
|
}
|
|
}
|
|
|
|
class Stats {
|
|
const Stats({
|
|
required this.str,
|
|
required this.con,
|
|
required this.dex,
|
|
required this.intelligence,
|
|
required this.wis,
|
|
required this.cha,
|
|
required this.hpMax,
|
|
required this.mpMax,
|
|
this.hpCurrent,
|
|
this.mpCurrent,
|
|
});
|
|
|
|
final int str;
|
|
final int con;
|
|
final int dex;
|
|
final int intelligence;
|
|
final int wis;
|
|
final int cha;
|
|
final int hpMax;
|
|
final int mpMax;
|
|
|
|
/// 현재 HP (null이면 hpMax와 동일)
|
|
final int? hpCurrent;
|
|
|
|
/// 현재 MP (null이면 mpMax와 동일)
|
|
final int? mpCurrent;
|
|
|
|
/// 실제 현재 HP 값
|
|
int get hp => hpCurrent ?? hpMax;
|
|
|
|
/// 실제 현재 MP 값
|
|
int get mp => mpCurrent ?? mpMax;
|
|
|
|
factory Stats.empty() => const Stats(
|
|
str: 0,
|
|
con: 0,
|
|
dex: 0,
|
|
intelligence: 0,
|
|
wis: 0,
|
|
cha: 0,
|
|
hpMax: 0,
|
|
mpMax: 0,
|
|
);
|
|
|
|
Stats copyWith({
|
|
int? str,
|
|
int? con,
|
|
int? dex,
|
|
int? intelligence,
|
|
int? wis,
|
|
int? cha,
|
|
int? hpMax,
|
|
int? mpMax,
|
|
int? hpCurrent,
|
|
int? mpCurrent,
|
|
}) {
|
|
return Stats(
|
|
str: str ?? this.str,
|
|
con: con ?? this.con,
|
|
dex: dex ?? this.dex,
|
|
intelligence: intelligence ?? this.intelligence,
|
|
wis: wis ?? this.wis,
|
|
cha: cha ?? this.cha,
|
|
hpMax: hpMax ?? this.hpMax,
|
|
mpMax: mpMax ?? this.mpMax,
|
|
hpCurrent: hpCurrent ?? this.hpCurrent,
|
|
mpCurrent: mpCurrent ?? this.mpCurrent,
|
|
);
|
|
}
|
|
}
|
|
|
|
class InventoryEntry {
|
|
const InventoryEntry({required this.name, required this.count});
|
|
|
|
final String name;
|
|
final int count;
|
|
|
|
InventoryEntry copyWith({String? name, int? count}) {
|
|
return InventoryEntry(name: name ?? this.name, count: count ?? this.count);
|
|
}
|
|
}
|
|
|
|
class Inventory {
|
|
const Inventory({required this.gold, required this.items});
|
|
|
|
final int gold;
|
|
final List<InventoryEntry> items;
|
|
|
|
factory Inventory.empty() => const Inventory(gold: 0, items: []);
|
|
|
|
Inventory copyWith({int? gold, List<InventoryEntry>? items}) {
|
|
return Inventory(gold: gold ?? this.gold, items: items ?? this.items);
|
|
}
|
|
}
|
|
|
|
/// 장비 (원본 Main.dfm Equips ListView, 11개 슬롯)
|
|
class Equipment {
|
|
const Equipment({
|
|
required this.weapon,
|
|
required this.shield,
|
|
required this.helm,
|
|
required this.hauberk,
|
|
required this.brassairts,
|
|
required this.vambraces,
|
|
required this.gauntlets,
|
|
required this.gambeson,
|
|
required this.cuisses,
|
|
required this.greaves,
|
|
required this.sollerets,
|
|
required this.bestIndex,
|
|
});
|
|
|
|
final String weapon; // 0: 무기
|
|
final String shield; // 1: 방패
|
|
final String helm; // 2: 투구
|
|
final String hauberk; // 3: 사슬갑옷
|
|
final String brassairts; // 4: 상완갑
|
|
final String vambraces; // 5: 전완갑
|
|
final String gauntlets; // 6: 건틀릿
|
|
final String gambeson; // 7: 갬비슨
|
|
final String cuisses; // 8: 허벅지갑
|
|
final String greaves; // 9: 정강이갑
|
|
final String sollerets; // 10: 철제신발
|
|
|
|
/// 최고 아이템 슬롯 인덱스 (원본 Equips.Tag, 0-10)
|
|
final int bestIndex;
|
|
|
|
/// 슬롯 개수
|
|
static const slotCount = 11;
|
|
|
|
factory Equipment.empty() => const Equipment(
|
|
weapon: 'Keyboard',
|
|
shield: '',
|
|
helm: '',
|
|
hauberk: '',
|
|
brassairts: '',
|
|
vambraces: '',
|
|
gauntlets: '',
|
|
gambeson: '',
|
|
cuisses: '',
|
|
greaves: '',
|
|
sollerets: '',
|
|
bestIndex: 0,
|
|
);
|
|
|
|
/// 인덱스로 슬롯 값 가져오기
|
|
String getByIndex(int index) {
|
|
return switch (index) {
|
|
0 => weapon,
|
|
1 => shield,
|
|
2 => helm,
|
|
3 => hauberk,
|
|
4 => brassairts,
|
|
5 => vambraces,
|
|
6 => gauntlets,
|
|
7 => gambeson,
|
|
8 => cuisses,
|
|
9 => greaves,
|
|
10 => sollerets,
|
|
_ => '',
|
|
};
|
|
}
|
|
|
|
/// 인덱스로 슬롯 값 설정한 새 Equipment 반환
|
|
Equipment setByIndex(int index, String value) {
|
|
return switch (index) {
|
|
0 => copyWith(weapon: value),
|
|
1 => copyWith(shield: value),
|
|
2 => copyWith(helm: value),
|
|
3 => copyWith(hauberk: value),
|
|
4 => copyWith(brassairts: value),
|
|
5 => copyWith(vambraces: value),
|
|
6 => copyWith(gauntlets: value),
|
|
7 => copyWith(gambeson: value),
|
|
8 => copyWith(cuisses: value),
|
|
9 => copyWith(greaves: value),
|
|
10 => copyWith(sollerets: value),
|
|
_ => this,
|
|
};
|
|
}
|
|
|
|
Equipment copyWith({
|
|
String? weapon,
|
|
String? shield,
|
|
String? helm,
|
|
String? hauberk,
|
|
String? brassairts,
|
|
String? vambraces,
|
|
String? gauntlets,
|
|
String? gambeson,
|
|
String? cuisses,
|
|
String? greaves,
|
|
String? sollerets,
|
|
int? bestIndex,
|
|
}) {
|
|
return Equipment(
|
|
weapon: weapon ?? this.weapon,
|
|
shield: shield ?? this.shield,
|
|
helm: helm ?? this.helm,
|
|
hauberk: hauberk ?? this.hauberk,
|
|
brassairts: brassairts ?? this.brassairts,
|
|
vambraces: vambraces ?? this.vambraces,
|
|
gauntlets: gauntlets ?? this.gauntlets,
|
|
gambeson: gambeson ?? this.gambeson,
|
|
cuisses: cuisses ?? this.cuisses,
|
|
greaves: greaves ?? this.greaves,
|
|
sollerets: sollerets ?? this.sollerets,
|
|
bestIndex: bestIndex ?? this.bestIndex,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SpellEntry {
|
|
const SpellEntry({required this.name, required this.rank});
|
|
|
|
final String name;
|
|
final String rank; // e.g., Roman numerals
|
|
|
|
SpellEntry copyWith({String? name, String? rank}) {
|
|
return SpellEntry(name: name ?? this.name, rank: rank ?? this.rank);
|
|
}
|
|
}
|
|
|
|
class SpellBook {
|
|
const SpellBook({required this.spells});
|
|
|
|
final List<SpellEntry> spells;
|
|
|
|
factory SpellBook.empty() => const SpellBook(spells: []);
|
|
|
|
SpellBook copyWith({List<SpellEntry>? spells}) {
|
|
return SpellBook(spells: spells ?? this.spells);
|
|
}
|
|
}
|
|
|
|
class ProgressBarState {
|
|
const ProgressBarState({required this.position, required this.max});
|
|
|
|
final int position;
|
|
final int max;
|
|
|
|
factory ProgressBarState.empty() =>
|
|
const ProgressBarState(position: 0, max: 1);
|
|
|
|
ProgressBarState copyWith({int? position, int? max}) {
|
|
return ProgressBarState(
|
|
position: position ?? this.position,
|
|
max: max ?? this.max,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 히스토리 엔트리 (Plot/Quest 진행 기록)
|
|
class HistoryEntry {
|
|
const HistoryEntry({required this.caption, required this.isComplete});
|
|
|
|
/// 표시 텍스트 (예: "Prologue", "Act I", "Exterminate the Goblins")
|
|
final String caption;
|
|
|
|
/// 완료 여부 (원본 StateIndex: 0=진행중, 1=완료)
|
|
final bool isComplete;
|
|
|
|
HistoryEntry copyWith({String? caption, bool? isComplete}) {
|
|
return HistoryEntry(
|
|
caption: caption ?? this.caption,
|
|
isComplete: isComplete ?? this.isComplete,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 현재 퀘스트 몬스터 정보 (원본 fQuest)
|
|
class QuestMonsterInfo {
|
|
const QuestMonsterInfo({
|
|
required this.monsterData,
|
|
required this.monsterIndex,
|
|
});
|
|
|
|
/// 몬스터 데이터 문자열 (예: "Goblin|3|ear")
|
|
final String monsterData;
|
|
|
|
/// 몬스터 인덱스 (Config.monsters에서의 인덱스)
|
|
final int monsterIndex;
|
|
|
|
static const empty = QuestMonsterInfo(monsterData: '', monsterIndex: -1);
|
|
}
|
|
|
|
class ProgressState {
|
|
const ProgressState({
|
|
required this.task,
|
|
required this.quest,
|
|
required this.plot,
|
|
required this.exp,
|
|
required this.encumbrance,
|
|
required this.currentTask,
|
|
required this.plotStageCount,
|
|
required this.questCount,
|
|
this.plotHistory = const [],
|
|
this.questHistory = const [],
|
|
this.currentQuestMonster,
|
|
});
|
|
|
|
final ProgressBarState task;
|
|
final ProgressBarState quest;
|
|
final ProgressBarState plot;
|
|
final ProgressBarState exp;
|
|
final ProgressBarState encumbrance;
|
|
final TaskInfo currentTask;
|
|
final int plotStageCount;
|
|
final int questCount;
|
|
|
|
/// 플롯 히스토리 (Prologue, Act I, Act II, ...)
|
|
final List<HistoryEntry> plotHistory;
|
|
|
|
/// 퀘스트 히스토리 (완료된/진행중인 퀘스트 목록)
|
|
final List<HistoryEntry> questHistory;
|
|
|
|
/// 현재 퀘스트 몬스터 정보 (Exterminate 타입용)
|
|
final QuestMonsterInfo? currentQuestMonster;
|
|
|
|
factory ProgressState.empty() => ProgressState(
|
|
task: ProgressBarState.empty(),
|
|
quest: ProgressBarState.empty(),
|
|
plot: ProgressBarState.empty(),
|
|
exp: ProgressBarState.empty(),
|
|
encumbrance: ProgressBarState.empty(),
|
|
currentTask: TaskInfo.empty(),
|
|
plotStageCount: 1, // Prologue
|
|
questCount: 0,
|
|
plotHistory: const [HistoryEntry(caption: 'Prologue', isComplete: false)],
|
|
questHistory: const [],
|
|
currentQuestMonster: null,
|
|
);
|
|
|
|
ProgressState copyWith({
|
|
ProgressBarState? task,
|
|
ProgressBarState? quest,
|
|
ProgressBarState? plot,
|
|
ProgressBarState? exp,
|
|
ProgressBarState? encumbrance,
|
|
TaskInfo? currentTask,
|
|
int? plotStageCount,
|
|
int? questCount,
|
|
List<HistoryEntry>? plotHistory,
|
|
List<HistoryEntry>? questHistory,
|
|
QuestMonsterInfo? currentQuestMonster,
|
|
}) {
|
|
return ProgressState(
|
|
task: task ?? this.task,
|
|
quest: quest ?? this.quest,
|
|
plot: plot ?? this.plot,
|
|
exp: exp ?? this.exp,
|
|
encumbrance: encumbrance ?? this.encumbrance,
|
|
currentTask: currentTask ?? this.currentTask,
|
|
plotStageCount: plotStageCount ?? this.plotStageCount,
|
|
questCount: questCount ?? this.questCount,
|
|
plotHistory: plotHistory ?? this.plotHistory,
|
|
questHistory: questHistory ?? this.questHistory,
|
|
currentQuestMonster: currentQuestMonster ?? this.currentQuestMonster,
|
|
);
|
|
}
|
|
}
|
|
|
|
class QueueEntry {
|
|
const QueueEntry({
|
|
required this.kind,
|
|
required this.durationMillis,
|
|
required this.caption,
|
|
this.taskType = TaskType.neutral,
|
|
});
|
|
|
|
final QueueKind kind;
|
|
final int durationMillis;
|
|
final String caption;
|
|
final TaskType taskType;
|
|
}
|
|
|
|
enum QueueKind { task, plot }
|
|
|
|
class QueueState {
|
|
QueueState({Iterable<QueueEntry>? entries})
|
|
: entries = Queue<QueueEntry>.from(entries ?? const []);
|
|
|
|
final Queue<QueueEntry> entries;
|
|
|
|
factory QueueState.empty() => QueueState(entries: const []);
|
|
|
|
QueueState copyWith({Iterable<QueueEntry>? entries}) {
|
|
return QueueState(entries: Queue<QueueEntry>.from(entries ?? this.entries));
|
|
}
|
|
}
|