Refactor Item Creation: Introduce LootGenerator
This commit is contained in:
parent
46658b22c8
commit
32c77dd20a
|
|
@ -4,8 +4,9 @@ import 'package:flutter/services.dart';
|
||||||
import '../model/item.dart';
|
import '../model/item.dart';
|
||||||
import '../enums.dart';
|
import '../enums.dart';
|
||||||
import '../config/item_config.dart';
|
import '../config/item_config.dart';
|
||||||
import 'item_prefix_table.dart'; // Import prefix table
|
// import 'item_prefix_table.dart'; // Logic moved to LootGenerator
|
||||||
import 'name_generator.dart'; // Import name generator
|
// import 'name_generator.dart'; // Logic moved to LootGenerator
|
||||||
|
import '../logic/loot_generator.dart'; // Import LootGenerator
|
||||||
import '../../utils/game_math.dart';
|
import '../../utils/game_math.dart';
|
||||||
|
|
||||||
class ItemTemplate {
|
class ItemTemplate {
|
||||||
|
|
@ -69,122 +70,9 @@ class ItemTemplate {
|
||||||
}
|
}
|
||||||
|
|
||||||
Item createItem({int stage = 1}) {
|
Item createItem({int stage = 1}) {
|
||||||
// Stage-based scaling is removed.
|
// Stage parameter kept for interface compatibility but unused here,
|
||||||
// Apply Prefix Logic based on Rarity.
|
// as scaling is now handled via Tier/Rarity in LootGenerator/Table logic.
|
||||||
|
return LootGenerator.generate(this);
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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` 확장 용이.
|
||||||
Loading…
Reference in New Issue