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 items; factory Inventory.empty() => const Inventory(gold: 0, items: []); Inventory copyWith({int? gold, List? 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 spells; factory SpellBook.empty() => const SpellBook(spells: []); SpellBook copyWith({List? 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 plotHistory; /// 퀘스트 히스토리 (완료된/진행중인 퀘스트 목록) final List 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? plotHistory, List? 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? entries}) : entries = Queue.from(entries ?? const []); final Queue entries; factory QueueState.empty() => QueueState(entries: const []); QueueState copyWith({Iterable? entries}) { return QueueState(entries: Queue.from(entries ?? this.entries)); } }