152 lines
4.2 KiB
Dart
152 lines
4.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
import 'package:flutter/services.dart';
|
|
import '../model/entity.dart';
|
|
import '../config/game_config.dart';
|
|
|
|
import 'item_table.dart';
|
|
|
|
class EnemyTemplate {
|
|
static const String fallbackImage = 'assets/images/enemies/Orc.png';
|
|
|
|
final String name;
|
|
final int baseHp;
|
|
final int baseAtk;
|
|
final int baseDefense;
|
|
final int baseDodge; // New: Base dodge chance
|
|
final String? image;
|
|
final List<String> equipmentIds;
|
|
final int tier;
|
|
|
|
const EnemyTemplate({
|
|
required this.name,
|
|
required this.baseHp,
|
|
required this.baseAtk,
|
|
required this.baseDefense,
|
|
this.baseDodge = 1, // Default value
|
|
this.image,
|
|
this.equipmentIds = const [],
|
|
this.tier = 1,
|
|
});
|
|
|
|
factory EnemyTemplate.fromJson(
|
|
Map<String, dynamic> json, {
|
|
Set<String>? availableAssets,
|
|
}) {
|
|
final imagePath = json['image'] as String?;
|
|
|
|
return EnemyTemplate(
|
|
name: json['name'],
|
|
baseHp: json['baseHp'] ?? 10,
|
|
baseAtk: json['baseAtk'] ?? 1,
|
|
baseDefense: json['baseDefense'] ?? 0,
|
|
baseDodge: json['baseDodge'] ?? 1, // Parse from JSON or default to 1
|
|
image: _resolveImagePath(imagePath, availableAssets),
|
|
equipmentIds: (json['equipment'] as List<dynamic>?)?.cast<String>() ?? [],
|
|
tier: json['tier'] ?? 1,
|
|
);
|
|
}
|
|
|
|
static String? _resolveImagePath(String? imagePath, Set<String>? assets) {
|
|
if (imagePath == null || imagePath.isEmpty) return null;
|
|
if (assets == null || assets.contains(imagePath)) return imagePath;
|
|
return fallbackImage;
|
|
}
|
|
|
|
Character createCharacter({int stage = 1}) {
|
|
// Stage-based scaling for enemy stats is removed to simplify balancing.
|
|
// Enemy stats are now fixed as defined in the EnemyTemplate.
|
|
|
|
final character = Character(
|
|
name: name,
|
|
maxHp: baseHp,
|
|
atk: baseAtk,
|
|
baseDefense: baseDefense,
|
|
baseDodge: baseDodge, // Pass baseDodge to Character constructor
|
|
armor: 0,
|
|
image: image,
|
|
);
|
|
|
|
// Equip items
|
|
for (final itemId in equipmentIds) {
|
|
final itemTemplate = ItemTable.get(itemId);
|
|
if (itemTemplate != null) {
|
|
// Items no longer scale by stage, pass no stage parameter
|
|
final item = itemTemplate.createItem();
|
|
character.addToInventory(item);
|
|
character.equip(item);
|
|
}
|
|
}
|
|
|
|
return character;
|
|
}
|
|
}
|
|
|
|
class EnemyTable {
|
|
static List<EnemyTemplate> normalEnemies = [];
|
|
static List<EnemyTemplate> eliteEnemies = [];
|
|
static final Random _random = Random();
|
|
|
|
static Future<void> load() async {
|
|
final String jsonString = await rootBundle.loadString(
|
|
'assets/data/enemies.json',
|
|
);
|
|
final Map<String, dynamic> data = jsonDecode(jsonString);
|
|
final availableAssets = await _loadAvailableAssets();
|
|
|
|
normalEnemies = (data['normal'] as List)
|
|
.map((e) => EnemyTemplate.fromJson(e, availableAssets: availableAssets))
|
|
.toList();
|
|
eliteEnemies = (data['elite'] as List)
|
|
.map((e) => EnemyTemplate.fromJson(e, availableAssets: availableAssets))
|
|
.toList();
|
|
}
|
|
|
|
static Future<Set<String>?> _loadAvailableAssets() async {
|
|
try {
|
|
final manifest = await AssetManifest.loadFromAssetBundle(rootBundle);
|
|
return manifest.listAssets().toSet();
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Returns a random enemy suitable for the current stage.
|
|
static EnemyTemplate getRandomEnemy({
|
|
required int stage,
|
|
bool isElite = false,
|
|
}) {
|
|
int targetTier = 1;
|
|
if (stage > GameConfig.tier2StageMax) {
|
|
targetTier = 3;
|
|
} else if (stage > GameConfig.tier1StageMax) {
|
|
targetTier = 2;
|
|
}
|
|
|
|
List<EnemyTemplate> pool = isElite ? eliteEnemies : normalEnemies;
|
|
|
|
// Filter by tier
|
|
var tierPool = pool.where((e) => e.tier == targetTier).toList();
|
|
|
|
// Fallback: If no enemies found for this tier, use lower tiers (or any)
|
|
if (tierPool.isEmpty) {
|
|
tierPool = pool.where((e) => e.tier <= targetTier).toList();
|
|
}
|
|
if (tierPool.isEmpty) {
|
|
tierPool = pool; // Absolute fallback
|
|
}
|
|
|
|
if (tierPool.isEmpty) {
|
|
// Should not happen if JSON is correct
|
|
return const EnemyTemplate(
|
|
name: "Fallback Enemy",
|
|
baseHp: 10,
|
|
baseAtk: 1,
|
|
baseDefense: 0,
|
|
);
|
|
}
|
|
|
|
return tierPool[_random.nextInt(tierPool.length)];
|
|
}
|
|
}
|