game/lib/game/data/item_table.dart

217 lines
6.6 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'package:flutter/services.dart';
import '../model/item.dart';
import '../enums.dart';
import '../config/item_config.dart';
// import 'item_prefix_table.dart'; // Logic moved to LootGenerator
// import 'name_generator.dart'; // Logic moved to LootGenerator
import '../logic/loot_generator.dart'; // Import LootGenerator
class ItemTemplate {
final String id;
final String name;
final String description;
final int atkBonus;
final int hpBonus;
final int armorBonus;
final EquipmentSlot slot;
final List<ItemEffect> effects;
final int price;
final String? image;
final int luck;
final ItemRarity rarity;
final ItemTier tier;
const ItemTemplate({
required this.id,
required this.name,
required this.description,
required this.atkBonus,
required this.hpBonus,
required this.armorBonus,
required this.slot,
required this.effects,
required this.price,
this.image,
this.luck = 0,
this.rarity = ItemRarity.magic,
this.tier = ItemTier.tier1,
});
factory ItemTemplate.fromJson(Map<String, dynamic> json) {
var effectsList = <ItemEffect>[];
if (json['effects'] != null) {
effectsList = (json['effects'] as List)
.map((e) => ItemEffect.fromJson(e))
.toList();
}
return ItemTemplate(
id: json['id'],
name: json['name'],
description: json['description'],
atkBonus: json['atkBonus'] ?? json['baseAtk'] ?? 0,
hpBonus: json['hpBonus'] ?? json['baseHp'] ?? 0,
armorBonus: json['armorBonus'] ?? json['baseArmor'] ?? 0,
slot: EquipmentSlot.values.firstWhere((e) => e.name == json['slot']),
effects: effectsList,
price: json['price'] ?? 10,
image: json['image'],
luck: json['luck'] ?? 0,
rarity: json['rarity'] != null
? ItemRarity.values.firstWhere((e) => e.name == json['rarity'])
: ItemRarity.magic,
tier: json['tier'] != null
? ItemTier.values.firstWhere((e) => e.name == json['tier'])
: ItemTier.tier1,
);
}
Item createItem({int stage = 1}) {
// 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);
}
}
class ItemTable {
static List<ItemTemplate> weapons = [];
static List<ItemTemplate> armors = [];
static List<ItemTemplate> shields = [];
static List<ItemTemplate> accessories = [];
static Future<void> load() async {
final String jsonString = await rootBundle.loadString(
'assets/data/items.json',
);
final Map<String, dynamic> data = jsonDecode(jsonString);
weapons = (data['weapons'] as List)
.map((e) => ItemTemplate.fromJson(e))
.toList();
armors = (data['armors'] as List)
.map((e) => ItemTemplate.fromJson(e))
.toList();
shields = (data['shields'] as List)
.map((e) => ItemTemplate.fromJson(e))
.toList();
accessories = (data['accessories'] as List)
.map((e) => ItemTemplate.fromJson(e))
.toList();
}
static List<ItemTemplate> get allItems => [
...weapons,
...armors,
...shields,
...accessories,
];
static ItemTemplate? get(String id) {
try {
return allItems.firstWhere((item) => item.id == id);
} catch (e) {
return null;
}
}
static final Random _random = Random();
/// Returns all items matching the given tier.
static List<ItemTemplate> getItemsByTier(ItemTier tier) {
return allItems.where((item) => item.tier == tier).toList();
}
/// Returns a random item based on Tier and Rarity weights.
///
/// [tier]: The tier of items to select from.
/// [slot]: Optional. If provided, only items of this slot are considered.
/// [weights]: Optional map of rarity weights. Key: Rarity, Value: Weight.
/// [minRarity]: Optional. Minimum rarity to consider (inclusive).
/// [maxRarity]: Optional. Maximum rarity to consider (inclusive).
static ItemTemplate? getRandomItem({
required ItemTier tier,
EquipmentSlot? slot,
Map<ItemRarity, int>? weights,
ItemRarity? minRarity,
ItemRarity? maxRarity,
}) {
// 1. Filter by Tier and Slot (if provided)
var candidates = allItems.where((item) => item.tier == tier);
if (slot != null) {
candidates = candidates.where((item) => item.slot == slot);
}
if (candidates.isEmpty) return null;
// 2. Prepare Rarity Weights (Filtered by min/max)
Map<ItemRarity, int> activeWeights = Map.from(
weights ?? ItemConfig.defaultRarityWeights,
);
if (minRarity != null) {
activeWeights.removeWhere((r, w) => r.index < minRarity.index);
}
if (maxRarity != null) {
activeWeights.removeWhere((r, w) => r.index > maxRarity.index);
}
if (activeWeights.isEmpty) {
// Fallback: If weights eliminated all options (e.g. misconfiguration),
// try to find ANY item within rarity range from candidates.
if (minRarity != null) {
candidates = candidates.where(
(item) => item.rarity.index >= minRarity.index,
);
}
if (maxRarity != null) {
candidates = candidates.where(
(item) => item.rarity.index <= maxRarity.index,
);
}
if (candidates.isEmpty) return null;
return candidates.toList()[_random.nextInt(candidates.length)];
}
// 3. Determine Target Rarity based on filtered weights
int totalWeight = activeWeights.values.fold(0, (sum, w) => sum + w);
int roll = _random.nextInt(totalWeight);
ItemRarity? selectedRarity;
int currentSum = 0;
for (var entry in activeWeights.entries) {
currentSum += entry.value;
if (roll < currentSum) {
selectedRarity = entry.key;
break;
}
}
// 4. Filter candidates by Selected Rarity
var rarityCandidates = candidates
.where((item) => item.rarity == selectedRarity)
.toList();
// 5. Fallback: If no items of selected rarity, use any item from the filtered candidates (respecting min/max)
if (rarityCandidates.isEmpty) {
if (minRarity != null) {
candidates = candidates.where(
(item) => item.rarity.index >= minRarity.index,
);
}
if (maxRarity != null) {
candidates = candidates.where(
(item) => item.rarity.index <= maxRarity.index,
);
}
if (candidates.isEmpty) return null;
return candidates.toList()[_random.nextInt(candidates.length)];
}
// 6. Pick random item
return rarityCandidates[_random.nextInt(rarityCandidates.length)];
}
}