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 '../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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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