This commit is contained in:
2025-12-07 17:58:00 +09:00
parent d5609aff0f
commit dcfb8ab9de
22 changed files with 612 additions and 165 deletions
+120 -92
View File
@@ -57,6 +57,10 @@ class BattleProvider with ChangeNotifier {
List<String> get logs => battleLogs;
int get lastGoldReward => _lastGoldReward;
void refreshUI() {
notifyListeners();
}
// Damage Event Stream
final _damageEventController = StreamController<DamageEvent>.broadcast();
Stream<DamageEvent> get damageStream => _damageEventController.stream;
@@ -83,10 +87,10 @@ class BattleProvider with ChangeNotifier {
stage = data['stage'];
turnCount = data['turnCount'];
player = Character.fromJson(data['player']);
battleLogs.clear();
_addLog("Game Loaded! Resuming Stage $stage");
_prepareNextStage();
notifyListeners();
}
@@ -113,51 +117,51 @@ class BattleProvider with ChangeNotifier {
player.gold = GameConfig.startingGold;
// Provide starter equipment
final starterSword = Item(
id: "starter_sword",
name: "Wooden Sword",
description: "A basic sword",
atkBonus: 5,
hpBonus: 0,
slot: EquipmentSlot.weapon,
);
final starterArmor = Item(
id: "starter_armor",
name: "Leather Armor",
description: "Basic protection",
atkBonus: 0,
hpBonus: 20,
slot: EquipmentSlot.armor,
);
final starterShield = Item(
id: "starter_shield",
name: "Wooden Shield",
description: "A small shield",
atkBonus: 0,
hpBonus: 0,
armorBonus: 3,
slot: EquipmentSlot.shield,
);
final starterRing = Item(
id: "starter_ring",
name: "Copper Ring",
description: "A simple ring",
atkBonus: 1,
hpBonus: 5,
slot: EquipmentSlot.accessory,
);
// final starterSword = Item(
// id: "starter_sword",
// name: "Wooden Sword",
// description: "A basic sword",
// atkBonus: 5,
// hpBonus: 0,
// slot: EquipmentSlot.weapon,
// );
// final starterArmor = Item(
// id: "starter_armor",
// name: "Leather Armor",
// description: "Basic protection",
// atkBonus: 0,
// hpBonus: 20,
// slot: EquipmentSlot.armor,
// );
// final starterShield = Item(
// id: "starter_shield",
// name: "Wooden Shield",
// description: "A small shield",
// atkBonus: 0,
// hpBonus: 0,
// armorBonus: 3,
// slot: EquipmentSlot.shield,
// );
// final starterRing = Item(
// id: "starter_ring",
// name: "Copper Ring",
// description: "A simple ring",
// atkBonus: 1,
// hpBonus: 5,
// slot: EquipmentSlot.accessory,
// );
player.addToInventory(starterSword);
player.equip(starterSword);
// player.addToInventory(starterSword);
// player.equip(starterSword);
player.addToInventory(starterArmor);
player.equip(starterArmor);
// player.addToInventory(starterArmor);
// player.equip(starterArmor);
player.addToInventory(starterShield);
player.equip(starterShield);
// player.addToInventory(starterShield);
// player.equip(starterShield);
player.addToInventory(starterRing);
player.equip(starterRing);
// player.addToInventory(starterRing);
// player.equip(starterRing);
// Add new status effect items for testing
player.addToInventory(ItemTable.weapons[3].createItem()); // Stunning Hammer
@@ -194,36 +198,8 @@ class BattleProvider with ChangeNotifier {
if (type == StageType.battle || type == StageType.elite) {
bool isElite = type == StageType.elite;
// Select random enemy template
final random = Random();
EnemyTemplate template;
if (isElite) {
if (EnemyTable.eliteEnemies.isNotEmpty) {
template = EnemyTable
.eliteEnemies[random.nextInt(EnemyTable.eliteEnemies.length)];
} else {
// Fallback if no elite enemies loaded
template = const EnemyTemplate(
name: "Elite Guardian",
baseHp: 50,
baseAtk: 10,
baseDefense: 2,
);
}
} else {
if (EnemyTable.normalEnemies.isNotEmpty) {
template = EnemyTable
.normalEnemies[random.nextInt(EnemyTable.normalEnemies.length)];
} else {
// Fallback
template = const EnemyTemplate(
name: "Enemy",
baseHp: 20,
baseAtk: 5,
baseDefense: 0,
);
}
}
EnemyTemplate template = EnemyTable.getRandomEnemy(stage: stage, isElite: isElite);
newEnemy = template.createCharacter(stage: stage);
@@ -264,6 +240,12 @@ class BattleProvider with ChangeNotifier {
// Replaces _spawnEnemy
// void _spawnEnemy() { ... } - Removed
Future<void> _onDefeat() async {
_addLog("Player defeated! Enemy wins!");
await SaveManager.clearSaveData();
notifyListeners();
}
/// Handle player's action choice
Future<void> playerAction(ActionType type, RiskLevel risk) async {
@@ -287,6 +269,12 @@ class BattleProvider with ChangeNotifier {
// 2. Process Start-of-Turn Effects (Stun, Bleed)
bool canAct = _processStartTurnEffects(player);
if (player.isDead) {
await _onDefeat();
return;
}
if (!canAct) {
_endPlayerTurn(); // Skip turn if stunned
return;
@@ -341,11 +329,17 @@ class BattleProvider with ChangeNotifier {
// Animation Delays to sync with Impact
if (risk == RiskLevel.safe) {
await Future.delayed(const Duration(milliseconds: GameConfig.animDelaySafe));
await Future.delayed(
const Duration(milliseconds: GameConfig.animDelaySafe),
);
} else if (risk == RiskLevel.normal) {
await Future.delayed(const Duration(milliseconds: GameConfig.animDelayNormal));
await Future.delayed(
const Duration(milliseconds: GameConfig.animDelayNormal),
);
} else if (risk == RiskLevel.risky) {
await Future.delayed(const Duration(milliseconds: GameConfig.animDelayRisky));
await Future.delayed(
const Duration(milliseconds: GameConfig.animDelayRisky),
);
}
int damageToHp = 0;
@@ -437,14 +431,19 @@ class BattleProvider with ChangeNotifier {
return;
}
Future.delayed(const Duration(milliseconds: GameConfig.animDelayEnemyTurn), () => _enemyTurn());
Future.delayed(
const Duration(milliseconds: GameConfig.animDelayEnemyTurn),
() => _enemyTurn(),
);
}
Future<void> _enemyTurn() async {
if (!isPlayerTurn && (player.isDead || enemy.isDead)) return;
_addLog("Enemy's turn...");
await Future.delayed(const Duration(milliseconds: GameConfig.animDelayEnemyTurn));
await Future.delayed(
const Duration(milliseconds: GameConfig.animDelayEnemyTurn),
);
// Enemy Turn Start Logic
// Armor decay
@@ -543,7 +542,8 @@ class BattleProvider with ChangeNotifier {
}
if (player.isDead) {
_addLog("Player defeated! Enemy wins!");
await _onDefeat();
return;
}
isPlayerTurn = true;
@@ -654,15 +654,6 @@ class BattleProvider with ChangeNotifier {
_addLog("Enemy defeated! Gained $goldReward Gold.");
_addLog("Choose a reward.");
List<ItemTemplate> allTemplates = List.from(ItemTable.allItems);
allTemplates.shuffle(random); // Shuffle to randomize selection
// Item Rewards
// Logic: Get random items based on current round tier? For now just random.
// Ideally should use ItemTable.getRandomItem() with Tier logic.
// Let's use our new weighted random logic if available, or fallback to simple shuffle for now to keep it simple.
// Since we just refactored ItemTable, let's use getRandomItem!
ItemTier currentTier = ItemTier.tier1;
if (stage > GameConfig.tier2StageMax)
currentTier = ItemTier.tier3;
@@ -670,9 +661,42 @@ class BattleProvider with ChangeNotifier {
currentTier = ItemTier.tier2;
rewardOptions = [];
// Get 3 distinct items if possible
bool isElite = currentStage.type == StageType.elite;
bool isTier1 = currentTier == ItemTier.tier1;
// Get 3 distinct items
for (int i = 0; i < 3; i++) {
ItemTemplate? item = ItemTable.getRandomItem(tier: currentTier);
ItemRarity? minRarity;
ItemRarity? maxRarity;
// 1. Elite Reward Logic (First Item only)
if (isElite && i == 0) {
if (isTier1) {
// Tier 1 Elite: Guaranteed Rare
minRarity = ItemRarity.rare;
maxRarity = ItemRarity.rare; // Or allow higher? Request said "Guaranteed Rare 1 drop". Let's fix to Rare.
} else {
// Tier 2/3 Elite: Guaranteed Legendary
minRarity = ItemRarity.legendary;
// maxRarity = ItemRarity.legendary; // Optional, but let's allow Unique too if weights permit, or fix to Legendary. Request said "Guaranteed Legendary".
}
}
// 2. Standard Reward Logic (Others)
else {
if (isTier1) {
// Tier 1 Normal/Other Rewards: Max Magic (No Rare+)
maxRarity = ItemRarity.magic;
}
// Tier 2/3 Normal: No extra restrictions
}
ItemTemplate? item = ItemTable.getRandomItem(
tier: currentTier,
minRarity: minRarity,
maxRarity: maxRarity
);
if (item != null) {
rewardOptions.add(item.createItem(stage: stage));
}
@@ -716,7 +740,9 @@ class BattleProvider with ChangeNotifier {
void _completeStage() {
// Heal player after selecting reward
int healAmount = GameMath.floor(player.totalMaxHp * GameConfig.stageHealRatio);
int healAmount = GameMath.floor(
player.totalMaxHp * GameConfig.stageHealRatio,
);
player.heal(healAmount);
_addLog("Stage Cleared! Recovered $healAmount HP.");
@@ -760,7 +786,9 @@ class BattleProvider with ChangeNotifier {
void sellItem(Item item) {
if (player.inventory.remove(item)) {
int sellPrice = GameMath.floor(item.price * GameConfig.sellPriceMultiplier);
int sellPrice = GameMath.floor(
item.price * GameConfig.sellPriceMultiplier,
);
player.gold += sellPrice;
_addLog("Sold ${item.name} for $sellPrice G.");
notifyListeners();