From 32c77dd20a8d1500636154d60e5dba869659694b Mon Sep 17 00:00:00 2001 From: Horoli Date: Sun, 7 Dec 2025 18:47:36 +0900 Subject: [PATCH] Refactor Item Creation: Introduce LootGenerator --- lib/game/data/item_table.dart | 124 ++------------------------ lib/game/logic/loot_generator.dart | 129 ++++++++++++++++++++++++++++ prompt/58_refactor_item_creation.md | 18 ++++ 3 files changed, 153 insertions(+), 118 deletions(-) create mode 100644 lib/game/logic/loot_generator.dart create mode 100644 prompt/58_refactor_item_creation.md diff --git a/lib/game/data/item_table.dart b/lib/game/data/item_table.dart index 486b290..7469aa8 100644 --- a/lib/game/data/item_table.dart +++ b/lib/game/data/item_table.dart @@ -4,8 +4,9 @@ import 'package:flutter/services.dart'; import '../model/item.dart'; import '../enums.dart'; import '../config/item_config.dart'; -import 'item_prefix_table.dart'; // Import prefix table -import 'name_generator.dart'; // Import name generator +// import 'item_prefix_table.dart'; // Logic moved to LootGenerator +// import 'name_generator.dart'; // Logic moved to LootGenerator +import '../logic/loot_generator.dart'; // Import LootGenerator import '../../utils/game_math.dart'; class ItemTemplate { @@ -69,122 +70,9 @@ class ItemTemplate { } Item createItem({int stage = 1}) { - // Stage-based scaling is removed. - // Apply Prefix Logic based on Rarity. - - String finalName = name; - int finalAtk = atkBonus; - int finalHp = hpBonus; - int finalArmor = armorBonus; - int finalLuck = luck; - - final random = Random(); - - // 0. Normal Rarity: Prefix logic for base stat variations - if (rarity == ItemRarity.normal) { - // Weighted Random Selection - final prefixes = ItemPrefixTable.normalPrefixes; - int totalWeight = prefixes.fold(0, (sum, p) => sum + p.weight); - int roll = random.nextInt(totalWeight); - - ItemModifier? selectedModifier; - int currentSum = 0; - for (var mod in prefixes) { - currentSum += mod.weight; - if (roll < currentSum) { - selectedModifier = mod; - break; - } - } - - if (selectedModifier != null) { - if (selectedModifier.prefix.isNotEmpty) { - finalName = "${selectedModifier.prefix} $name"; - } - - double mult = selectedModifier.multiplier; - if (mult != 1.0) { - finalAtk = (finalAtk * mult).floor(); - finalHp = (finalHp * mult).floor(); - finalArmor = (finalArmor * mult).floor(); - // Luck usually isn't scaled by small multipliers, but let's keep it consistent or skip. - // Skipping luck scaling for normal prefixes to avoid 0. - } - } - } - // 1. Magic Rarity: 50% chance to get a Magic Prefix (1 stat change) - else if (rarity == ItemRarity.magic) { - if (random.nextBool()) { // 50% chance - // Filter valid prefixes for this slot - final validPrefixes = ItemPrefixTable.magicPrefixes.where((p) { - return p.allowedSlots == null || p.allowedSlots!.contains(slot); - }).toList(); - - if (validPrefixes.isNotEmpty) { - final modifier = validPrefixes[random.nextInt(validPrefixes.length)]; - finalName = "${modifier.prefix} $name"; - - modifier.statChanges.forEach((stat, value) { - switch(stat) { - case StatType.atk: finalAtk += value; break; - case StatType.maxHp: finalHp += value; break; - case StatType.defense: finalArmor += value; break; - case StatType.luck: finalLuck += value; break; - } - }); - } - } - } - // 2. Rare Rarity: Always get a Rare Prefix (2 stat changes or stronger) - else if (rarity == ItemRarity.rare) { - bool nameChanged = false; - - // Always generate a completely new cool name for Rare items - finalName = NameGenerator.generateName(slot); - nameChanged = true; - - // Filter valid prefixes for this slot - final validPrefixes = ItemPrefixTable.rarePrefixes.where((p) { - return p.allowedSlots == null || p.allowedSlots!.contains(slot); - }).toList(); - - if (validPrefixes.isNotEmpty) { - final modifier = validPrefixes[random.nextInt(validPrefixes.length)]; - - // If name wasn't already changed by NameGenerator, apply prefix to name - if (!nameChanged) { - finalName = "${modifier.prefix} $name"; - } - // Even if name changed, we STILL apply the stats from the prefix modifier! - // Because NameGenerator is just visual flavor, stats come from the modifier. - - modifier.statChanges.forEach((stat, value) { - switch(stat) { - case StatType.atk: finalAtk += value; break; - case StatType.maxHp: finalHp += value; break; - case StatType.defense: finalArmor += value; break; - case StatType.luck: finalLuck += value; break; - } - }); - } - } - // Legendary/Unique items usually keep their original names/stats as they are special. - - return Item( - id: id, - name: finalName, - description: description, - atkBonus: finalAtk, - hpBonus: finalHp, - armorBonus: finalArmor, - slot: slot, - effects: effects, - price: price, - image: image, - luck: finalLuck, - rarity: rarity, - tier: tier, - ); + // Stage parameter kept for interface compatibility but unused here, + // as scaling is now handled via Tier/Rarity in LootGenerator/Table logic. + return LootGenerator.generate(this); } } diff --git a/lib/game/logic/loot_generator.dart b/lib/game/logic/loot_generator.dart new file mode 100644 index 0000000..d8f0f3d --- /dev/null +++ b/lib/game/logic/loot_generator.dart @@ -0,0 +1,129 @@ +import 'dart:math'; +import '../model/item.dart'; +import '../data/item_table.dart'; // For ItemTemplate +import '../data/item_prefix_table.dart'; +import '../data/name_generator.dart'; +import '../enums.dart'; + +class LootGenerator { + static final Random _random = Random(); + + /// Generates an Item instance from a template, applying prefixes/suffixes based on rarity. + static Item generate(ItemTemplate template) { + String finalName = template.name; + int finalAtk = template.atkBonus; + int finalHp = template.hpBonus; + int finalArmor = template.armorBonus; + int finalLuck = template.luck; + + // 0. Normal Rarity: Prefix logic for base stat variations + if (template.rarity == ItemRarity.normal) { + // Weighted Random Selection + final prefixes = ItemPrefixTable.normalPrefixes; + int totalWeight = prefixes.fold(0, (sum, p) => sum + p.weight); + int roll = _random.nextInt(totalWeight); + + ItemModifier? selectedModifier; + int currentSum = 0; + for (var mod in prefixes) { + currentSum += mod.weight; + if (roll < currentSum) { + selectedModifier = mod; + break; + } + } + + if (selectedModifier != null) { + if (selectedModifier.prefix.isNotEmpty) { + finalName = "${selectedModifier.prefix} $template.name"; + } + + double mult = selectedModifier.multiplier; + if (mult != 1.0) { + finalAtk = (finalAtk * mult).floor(); + finalHp = (finalHp * mult).floor(); + finalArmor = (finalArmor * mult).floor(); + } + } + } + // 1. Magic Rarity: 50% chance to get a Magic Prefix (1 stat change) + else if (template.rarity == ItemRarity.magic) { + if (_random.nextBool()) { // 50% chance + // Filter valid prefixes for this slot + final validPrefixes = ItemPrefixTable.magicPrefixes.where((p) { + return p.allowedSlots == null || p.allowedSlots!.contains(template.slot); + }).toList(); + + if (validPrefixes.isNotEmpty) { + final modifier = validPrefixes[_random.nextInt(validPrefixes.length)]; + finalName = "${modifier.prefix} $template.name"; + + modifier.statChanges.forEach((stat, value) { + switch(stat) { + case StatType.atk: finalAtk += value; break; + case StatType.maxHp: finalHp += value; break; + case StatType.defense: finalArmor += value; break; + case StatType.luck: finalLuck += value; break; + } + }); + } + } + } + // 2. Rare Rarity: Always get a Rare Prefix (2 stat changes or stronger) + else if (template.rarity == ItemRarity.rare) { + bool nameChanged = false; + + // Always generate a completely new cool name for Rare items + finalName = NameGenerator.generateName(template.slot); + nameChanged = true; + + // Filter valid prefixes for this slot + final validPrefixes = ItemPrefixTable.rarePrefixes.where((p) { + return p.allowedSlots == null || p.allowedSlots!.contains(template.slot); + }).toList(); + + if (validPrefixes.isNotEmpty) { + final modifier = validPrefixes[_random.nextInt(validPrefixes.length)]; + + // If name wasn't already changed by NameGenerator (fallback?), apply prefix to name + // But NameGenerator always returns a name. + // However, if we want to combine "Prefix" + "GeneratedName", we could. + // Current logic in ItemTable was: + if (!nameChanged) { + finalName = "${modifier.prefix} $template.name"; + } + // Wait, the logic in ItemTable says: + // "If name wasn't already changed by NameGenerator, apply prefix to name" + // But just above it says "nameChanged = true;". So it never enters inside? + // Actually, NameGenerator might fail? No, it's static data. + // Let's stick to the logic: Rare items use generated names, but get STATS from prefix. + + modifier.statChanges.forEach((stat, value) { + switch(stat) { + case StatType.atk: finalAtk += value; break; + case StatType.maxHp: finalHp += value; break; + case StatType.defense: finalArmor += value; break; + case StatType.luck: finalLuck += value; break; + } + }); + } + } + // Legendary/Unique items usually keep their original names/stats. + + return Item( + id: template.id, + name: finalName, + description: template.description, + atkBonus: finalAtk, + hpBonus: finalHp, + armorBonus: finalArmor, + slot: template.slot, + effects: template.effects, + price: template.price, + image: template.image, + luck: finalLuck, + rarity: template.rarity, + tier: template.tier, + ); + } +} diff --git a/prompt/58_refactor_item_creation.md b/prompt/58_refactor_item_creation.md new file mode 100644 index 0000000..54a86e7 --- /dev/null +++ b/prompt/58_refactor_item_creation.md @@ -0,0 +1,18 @@ +# 58. Refactor Item Creation Logic + +## 1. 목표 (Goal) +- `ItemTemplate` 클래스 내부에 강하게 결합된 아이템 생성 및 접두사(Prefix) 적용 로직을 분리합니다. +- `LootGenerator` 클래스를 생성하여 전리품 생성 및 옵션 부여 로직을 중앙화합니다. + +## 2. 구현 계획 (Implementation Plan) +1. **`LootGenerator` 생성 (`lib/game/logic/loot_generator.dart`):** + - `ItemTemplate`을 입력받아 실제 `Item` 객체를 생성하는 static 메서드 `generate`를 구현합니다. + - 기존 `createItem`에 있던 Rarity별 접두사 처리, 스탯 보정, 이름 변경 로직을 이곳으로 이동합니다. +2. **`ItemTemplate` 수정 (`lib/game/data/item_table.dart`):** + - `createItem` 메서드가 `LootGenerator`를 호출하도록 변경하거나, 해당 메서드를 제거하고 호출부(`BattleProvider`, `EnemyTemplate`)에서 `LootGenerator`를 직접 사용하도록 수정합니다. + - (호환성을 위해 `createItem`은 `LootGenerator`를 호출하는 래퍼로 남겨두는 것을 권장) + +## 3. 기대 효과 (Expected Outcome) +- `ItemTemplate`은 순수한 데이터 정의(DTO) 역할에 집중. +- 아이템 생성 알고리즘(접두사, 랜덤 스탯 등)이 변경되더라도 데이터 클래스에는 영향 없음. +- 추후 '제작(Crafting)' 시스템이나 '상점 전용 생성' 등 다양한 생성 규칙 추가 시 `LootGenerator` 확장 용이.