Files
game/lib/game/data/enemy_table.dart
T

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)];
}
}