update
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import '../model/item.dart';
|
||||
import '../model/status_effect.dart'; // Import StatusEffect for ItemEffect
|
||||
|
||||
class ItemTemplate {
|
||||
final String name;
|
||||
@@ -7,6 +8,7 @@ class ItemTemplate {
|
||||
final int baseHp;
|
||||
final int baseArmor;
|
||||
final EquipmentSlot slot;
|
||||
final List<ItemEffect> effects; // New: Effects this item can inflict
|
||||
|
||||
const ItemTemplate({
|
||||
required this.name,
|
||||
@@ -15,6 +17,7 @@ class ItemTemplate {
|
||||
this.baseHp = 0,
|
||||
this.baseArmor = 0,
|
||||
required this.slot,
|
||||
this.effects = const [], // Default to no effects
|
||||
});
|
||||
|
||||
// Create an instance of Item based on this template, optionally scaling with stage
|
||||
@@ -25,6 +28,13 @@ class ItemTemplate {
|
||||
int scaledHp = baseHp > 0 ? baseHp + (stage - 1) * 5 : 0;
|
||||
int scaledArmor = baseArmor > 0 ? baseArmor + (stage - 1) : 0;
|
||||
|
||||
// Calculate price based on stats
|
||||
int calculatedPrice = (scaledAtk * 10) + (scaledHp * 2) + (scaledArmor * 5);
|
||||
if (effects.isNotEmpty) {
|
||||
calculatedPrice += effects.length * 50; // Bonus value for special effects
|
||||
}
|
||||
if (calculatedPrice < 10) calculatedPrice = 10; // Minimum price
|
||||
|
||||
return Item(
|
||||
name: "$name${stage > 1 ? ' +${stage - 1}' : ''}", // Append +1, +2 etc.
|
||||
description: description,
|
||||
@@ -32,30 +42,73 @@ class ItemTemplate {
|
||||
hpBonus: scaledHp,
|
||||
armorBonus: scaledArmor,
|
||||
slot: slot,
|
||||
effects: effects, // Pass the effects to the Item
|
||||
price: calculatedPrice,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemTable {
|
||||
static const List<ItemTemplate> weapons = [
|
||||
ItemTemplate(
|
||||
static final List<ItemTemplate> weapons = [
|
||||
const ItemTemplate(
|
||||
name: "Rusty Dagger",
|
||||
description: "Old and rusty, but better than nothing.",
|
||||
baseAtk: 3,
|
||||
slot: EquipmentSlot.weapon,
|
||||
),
|
||||
ItemTemplate(
|
||||
const ItemTemplate(
|
||||
name: "Iron Sword",
|
||||
description: "A standard soldier's sword.",
|
||||
baseAtk: 8,
|
||||
slot: EquipmentSlot.weapon,
|
||||
),
|
||||
ItemTemplate(
|
||||
const ItemTemplate(
|
||||
name: "Battle Axe",
|
||||
description: "Heavy but powerful.",
|
||||
baseAtk: 12,
|
||||
slot: EquipmentSlot.weapon,
|
||||
),
|
||||
// New: Weapons with status effects
|
||||
ItemTemplate(
|
||||
name: "Stunning Hammer",
|
||||
description: "A heavy hammer that can stun foes.",
|
||||
baseAtk: 10,
|
||||
slot: EquipmentSlot.weapon,
|
||||
effects: [
|
||||
ItemEffect(
|
||||
type: StatusEffectType.stun,
|
||||
probability: 20,
|
||||
duration: 1,
|
||||
), // 20% chance to stun for 1 turn
|
||||
],
|
||||
),
|
||||
ItemTemplate(
|
||||
name: "Jagged Dagger",
|
||||
description: "A cruel dagger that causes bleeding.",
|
||||
baseAtk: 7,
|
||||
slot: EquipmentSlot.weapon,
|
||||
effects: [
|
||||
ItemEffect(
|
||||
type: StatusEffectType.bleed,
|
||||
probability: 30,
|
||||
duration: 3,
|
||||
value: 5,
|
||||
), // 30% chance to bleed (5 dmg/turn for 3 turns)
|
||||
],
|
||||
),
|
||||
ItemTemplate(
|
||||
name: "Sunderer Axe",
|
||||
description: "An axe that exposes enemy weaknesses.",
|
||||
baseAtk: 11,
|
||||
slot: EquipmentSlot.weapon,
|
||||
effects: [
|
||||
ItemEffect(
|
||||
type: StatusEffectType.vulnerable,
|
||||
probability: 100,
|
||||
duration: 2,
|
||||
), // 100% chance to make vulnerable for 2 turns
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
static const List<ItemTemplate> armors = [
|
||||
@@ -79,25 +132,40 @@ class ItemTable {
|
||||
),
|
||||
];
|
||||
|
||||
static const List<ItemTemplate> shields = [
|
||||
ItemTemplate(
|
||||
static final List<ItemTemplate> shields = [
|
||||
const ItemTemplate(
|
||||
name: "Pot Lid",
|
||||
description: "It was used for cooking.",
|
||||
baseArmor: 1,
|
||||
slot: EquipmentSlot.shield,
|
||||
),
|
||||
ItemTemplate(
|
||||
const ItemTemplate(
|
||||
name: "Wooden Shield",
|
||||
description: "Sturdy oak wood.",
|
||||
baseArmor: 3,
|
||||
slot: EquipmentSlot.shield,
|
||||
),
|
||||
ItemTemplate(
|
||||
const ItemTemplate(
|
||||
name: "Kite Shield",
|
||||
description: "Used by knights.",
|
||||
baseArmor: 6,
|
||||
slot: EquipmentSlot.shield,
|
||||
),
|
||||
// New: Shield with Defense Forbidden effect (example)
|
||||
ItemTemplate(
|
||||
name: "Cursed Shield",
|
||||
description:
|
||||
"A shield that prevents the wielder from defending themselves.",
|
||||
baseArmor: 5,
|
||||
slot: EquipmentSlot.shield,
|
||||
effects: [
|
||||
ItemEffect(
|
||||
type: StatusEffectType.defenseForbidden,
|
||||
probability: 100,
|
||||
duration: 999,
|
||||
), // Always prevent defending (long duration for testing)
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
static const List<ItemTemplate> accessories = [
|
||||
@@ -108,6 +176,13 @@ class ItemTable {
|
||||
baseHp: 5,
|
||||
slot: EquipmentSlot.accessory,
|
||||
),
|
||||
ItemTemplate(
|
||||
name: "Copper Ring",
|
||||
description: "A simple ring",
|
||||
baseAtk: 1,
|
||||
baseHp: 5,
|
||||
slot: EquipmentSlot.accessory,
|
||||
),
|
||||
ItemTemplate(
|
||||
name: "Ruby Amulet",
|
||||
description: "Glows with a faint red light.",
|
||||
@@ -126,9 +201,9 @@ class ItemTable {
|
||||
];
|
||||
|
||||
static List<ItemTemplate> get allItems => [
|
||||
...weapons,
|
||||
...armors,
|
||||
...shields,
|
||||
...accessories,
|
||||
];
|
||||
...weapons,
|
||||
...armors,
|
||||
...shields,
|
||||
...accessories,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'item.dart';
|
||||
import 'status_effect.dart';
|
||||
|
||||
class Character {
|
||||
String name;
|
||||
@@ -7,10 +8,14 @@ class Character {
|
||||
int armor; // Current temporary shield/armor points in battle
|
||||
int baseAtk;
|
||||
int baseDefense; // Base defense stat
|
||||
int gold; // New: Currency
|
||||
Map<EquipmentSlot, Item> equipment = {};
|
||||
List<Item> inventory = [];
|
||||
final int maxInventorySize = 16;
|
||||
|
||||
// Active status effects
|
||||
List<StatusEffect> statusEffects = [];
|
||||
|
||||
Character({
|
||||
required this.name,
|
||||
int? hp,
|
||||
@@ -18,10 +23,50 @@ class Character {
|
||||
required this.armor,
|
||||
required int atk,
|
||||
this.baseDefense = 0,
|
||||
this.gold = 0,
|
||||
}) : baseMaxHp = maxHp,
|
||||
baseAtk = atk,
|
||||
hp = hp ?? maxHp;
|
||||
|
||||
/// Adds a status effect. If it already exists, it refreshes duration or stacks based on logic.
|
||||
/// For now, we'll implement a simple refresh/overwrite logic.
|
||||
void addStatusEffect(StatusEffect newEffect) {
|
||||
// Check if effect exists
|
||||
var existing = statusEffects
|
||||
.where((e) => e.type == newEffect.type)
|
||||
.firstOrNull;
|
||||
|
||||
if (existing != null) {
|
||||
// Refresh duration if the new one is longer, or just reset it?
|
||||
// Let's max the duration for now.
|
||||
if (newEffect.duration > existing.duration) {
|
||||
existing.duration = newEffect.duration;
|
||||
}
|
||||
// Logic for 'value' (stacking bleed?) can be added here.
|
||||
} else {
|
||||
statusEffects.add(newEffect);
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrements duration of all effects and removes expired ones.
|
||||
/// Returns a list of expired effects if needed for UI logs.
|
||||
void updateStatusEffects() {
|
||||
// Remove effects with 0 or less duration first (safety cleanup)
|
||||
statusEffects.removeWhere((e) => e.duration <= 0);
|
||||
|
||||
for (var effect in statusEffects) {
|
||||
effect.duration--;
|
||||
}
|
||||
|
||||
// Remove effects that just expired (duration went to 0 or -1)
|
||||
statusEffects.removeWhere((e) => e.duration <= 0);
|
||||
}
|
||||
|
||||
/// Helper to check if character has a specific status
|
||||
bool hasStatus(StatusEffectType type) {
|
||||
return statusEffects.any((e) => e.type == type);
|
||||
}
|
||||
|
||||
int get totalMaxHp {
|
||||
int bonus = equipment.values.fold(0, (sum, item) => sum + item.hpBonus);
|
||||
return baseMaxHp + bonus;
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
import 'status_effect.dart';
|
||||
|
||||
enum EquipmentSlot { weapon, armor, shield, accessory }
|
||||
|
||||
/// Defines an effect that an item can apply (e.g., 10% chance to Stun for 1 turn)
|
||||
class ItemEffect {
|
||||
final StatusEffectType type;
|
||||
final int probability; // 0 to 100
|
||||
final int duration;
|
||||
final int value; // e.g., bleed damage amount
|
||||
|
||||
ItemEffect({
|
||||
required this.type,
|
||||
required this.probability,
|
||||
required this.duration,
|
||||
this.value = 0,
|
||||
});
|
||||
|
||||
String get description {
|
||||
String typeStr = type.name.toUpperCase();
|
||||
// Customize names if needed
|
||||
if (type == StatusEffectType.defenseForbidden) typeStr = "UNBLOCKABLE";
|
||||
|
||||
String durationStr = "${duration}t";
|
||||
String valStr = value > 0 ? " ($value dmg)" : "";
|
||||
|
||||
return "$typeStr ${probability}% ($durationStr)$valStr";
|
||||
}
|
||||
}
|
||||
|
||||
class Item {
|
||||
final String name;
|
||||
final String description;
|
||||
@@ -7,6 +35,8 @@ class Item {
|
||||
final int hpBonus;
|
||||
final int armorBonus; // New stat for defense
|
||||
final EquipmentSlot slot;
|
||||
final List<ItemEffect> effects; // Status effects this item can inflict
|
||||
final int price; // New: Sell/Buy value
|
||||
|
||||
Item({
|
||||
required this.name,
|
||||
@@ -15,6 +45,8 @@ class Item {
|
||||
required this.hpBonus,
|
||||
this.armorBonus = 0, // Default to 0 for backward compatibility
|
||||
required this.slot,
|
||||
this.effects = const [], // Default to no effects
|
||||
this.price = 0,
|
||||
});
|
||||
|
||||
String get typeName {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'entity.dart';
|
||||
import 'item.dart';
|
||||
|
||||
enum StageType {
|
||||
battle, // Normal battle
|
||||
elite, // Stronger enemy
|
||||
shop, // Buy/Sell items
|
||||
rest, // Heal or repair
|
||||
}
|
||||
|
||||
class StageModel {
|
||||
final StageType type;
|
||||
final Character? enemy; // For battle/elite
|
||||
final List<Item> shopItems; // For shop
|
||||
|
||||
StageModel({
|
||||
required this.type,
|
||||
this.enemy,
|
||||
this.shopItems = const [],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
enum StatusEffectType {
|
||||
stun, // Cannot act this turn
|
||||
vulnerable, // Takes 50% more damage
|
||||
bleed, // Takes damage at start/end of turn
|
||||
defenseForbidden, // Cannot use Defend action
|
||||
}
|
||||
|
||||
class StatusEffect {
|
||||
final StatusEffectType type;
|
||||
int duration; // Turns remaining
|
||||
final int value; // Intensity (e.g., bleed damage amount)
|
||||
|
||||
StatusEffect({
|
||||
required this.type,
|
||||
required this.duration,
|
||||
this.value = 0,
|
||||
});
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'providers/battle_provider.dart';
|
||||
import 'screens/main_wrapper.dart';
|
||||
import 'screens/main_menu_screen.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@@ -19,7 +19,7 @@ class MyApp extends StatelessWidget {
|
||||
child: MaterialApp(
|
||||
title: "Colosseum's Choice",
|
||||
theme: ThemeData.dark(),
|
||||
home: const MainWrapper(),
|
||||
home: const MainMenuScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,21 @@ import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../game/model/entity.dart';
|
||||
import '../game/model/item.dart';
|
||||
import '../game/data/item_table.dart'; // Import ItemTable
|
||||
import '../utils/game_math.dart'; // Import GameMath
|
||||
import '../game/model/status_effect.dart';
|
||||
import '../game/model/stage.dart'; // Import StageModel
|
||||
import '../game/data/item_table.dart';
|
||||
import '../utils/game_math.dart';
|
||||
|
||||
enum ActionType { attack, defend }
|
||||
|
||||
enum RiskLevel { safe, normal, risky }
|
||||
|
||||
class BattleProvider with ChangeNotifier {
|
||||
late Character player;
|
||||
late Character enemy;
|
||||
late Character enemy; // Kept for compatibility, active during Battle/Elite
|
||||
|
||||
late StageModel currentStage; // The current stage object
|
||||
|
||||
List<String> battleLogs = [];
|
||||
bool isPlayerTurn = true;
|
||||
|
||||
@@ -20,51 +26,169 @@ class BattleProvider with ChangeNotifier {
|
||||
bool showRewardPopup = false;
|
||||
|
||||
BattleProvider() {
|
||||
initializeBattle();
|
||||
// initializeBattle(); // Do not auto-start logic
|
||||
}
|
||||
|
||||
void initializeBattle() {
|
||||
stage = 1;
|
||||
player = Character(name: "Player", maxHp: 100, armor: 0, atk: 10, baseDefense: 5); // Added baseDefense 5
|
||||
|
||||
player = Character(
|
||||
name: "Player",
|
||||
maxHp: 100,
|
||||
armor: 0,
|
||||
atk: 10,
|
||||
baseDefense: 5,
|
||||
);
|
||||
|
||||
// Provide starter equipment
|
||||
final starterSword = Item(name: "Wooden Sword", description: "A basic sword", atkBonus: 5, hpBonus: 0, slot: EquipmentSlot.weapon);
|
||||
final starterArmor = Item(name: "Leather Armor", description: "Basic protection", atkBonus: 0, hpBonus: 20, slot: EquipmentSlot.armor);
|
||||
final starterShield = Item(name: "Wooden Shield", description: "A small shield", atkBonus: 0, hpBonus: 0, armorBonus: 3, slot: EquipmentSlot.shield);
|
||||
final starterRing = Item(name: "Copper Ring", description: "A simple ring", atkBonus: 1, hpBonus: 5, slot: EquipmentSlot.accessory);
|
||||
final starterSword = Item(
|
||||
name: "Wooden Sword",
|
||||
description: "A basic sword",
|
||||
atkBonus: 5,
|
||||
hpBonus: 0,
|
||||
slot: EquipmentSlot.weapon,
|
||||
);
|
||||
final starterArmor = Item(
|
||||
name: "Leather Armor",
|
||||
description: "Basic protection",
|
||||
atkBonus: 0,
|
||||
hpBonus: 20,
|
||||
slot: EquipmentSlot.armor,
|
||||
);
|
||||
final starterShield = Item(
|
||||
name: "Wooden Shield",
|
||||
description: "A small shield",
|
||||
atkBonus: 0,
|
||||
hpBonus: 0,
|
||||
armorBonus: 3,
|
||||
slot: EquipmentSlot.shield,
|
||||
);
|
||||
final starterRing = Item(
|
||||
name: "Copper Ring",
|
||||
description: "A simple ring",
|
||||
atkBonus: 1,
|
||||
hpBonus: 5,
|
||||
slot: EquipmentSlot.accessory,
|
||||
);
|
||||
|
||||
player.addToInventory(starterSword);
|
||||
player.equip(starterSword);
|
||||
|
||||
|
||||
player.addToInventory(starterArmor);
|
||||
player.equip(starterArmor);
|
||||
|
||||
|
||||
player.addToInventory(starterShield);
|
||||
player.equip(starterShield);
|
||||
|
||||
|
||||
player.addToInventory(starterRing);
|
||||
player.equip(starterRing);
|
||||
|
||||
_spawnEnemy();
|
||||
// Add new status effect items for testing
|
||||
player.addToInventory(ItemTable.weapons[3].createItem()); // Stunning Hammer
|
||||
player.addToInventory(ItemTable.weapons[4].createItem()); // Jagged Dagger
|
||||
player.addToInventory(ItemTable.weapons[5].createItem()); // Sunderer Axe
|
||||
player.addToInventory(ItemTable.shields[3].createItem()); // Cursed Shield
|
||||
|
||||
_prepareNextStage();
|
||||
battleLogs.clear();
|
||||
_addLog("Battle started! Stage $stage");
|
||||
isPlayerTurn = true;
|
||||
showRewardPopup = false;
|
||||
_addLog("Game Started! Stage 1");
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _spawnEnemy() {
|
||||
int enemyHp = 5 + (stage - 1) * 20;
|
||||
int enemyAtk = 8 + (stage - 1) * 2;
|
||||
enemy = Character(name: "Enemy", maxHp: enemyHp, armor: 0, atk: enemyAtk);
|
||||
void _prepareNextStage() {
|
||||
StageType type;
|
||||
|
||||
// Stage Type Logic
|
||||
if (stage % 10 == 0) {
|
||||
type = StageType.elite; // Every 10th stage is a Boss/Elite
|
||||
} else if (stage % 5 == 0) {
|
||||
type = StageType.shop; // Every 5th stage is a Shop (except 10, 20...)
|
||||
} else if (stage % 8 == 0) {
|
||||
type = StageType.rest; // Every 8th stage is a Rest
|
||||
} else {
|
||||
type = StageType.battle;
|
||||
}
|
||||
|
||||
// Prepare Data based on Type
|
||||
Character? newEnemy;
|
||||
List<Item> shopItems = [];
|
||||
|
||||
if (type == StageType.battle || type == StageType.elite) {
|
||||
bool isElite = type == StageType.elite;
|
||||
int hpMultiplier = isElite ? 1 : 1;
|
||||
int atkMultiplier = isElite ? 4 : 2;
|
||||
|
||||
int enemyHp = 1 + (stage - 1) * hpMultiplier;
|
||||
int enemyAtk = 8 + (stage - 1) * atkMultiplier;
|
||||
|
||||
String name = isElite ? "Elite Guardian" : "Enemy";
|
||||
newEnemy = Character(name: name, maxHp: enemyHp, armor: 0, atk: enemyAtk);
|
||||
|
||||
// Assign to the main 'enemy' field for UI compatibility
|
||||
enemy = newEnemy;
|
||||
isPlayerTurn = true;
|
||||
showRewardPopup = false;
|
||||
|
||||
_addLog("Stage $stage ($type) started! A wild ${enemy.name} appeared.");
|
||||
} else if (type == StageType.shop) {
|
||||
// Generate random items for shop
|
||||
final random = Random();
|
||||
List<ItemTemplate> allTemplates = List.from(ItemTable.allItems);
|
||||
allTemplates.shuffle(random);
|
||||
|
||||
int count = min(4, allTemplates.length);
|
||||
shopItems = allTemplates
|
||||
.sublist(0, count)
|
||||
.map((t) => t.createItem(stage: stage))
|
||||
.toList();
|
||||
|
||||
// Dummy enemy to prevent null errors in existing UI (until UI is fully updated)
|
||||
enemy = Character(name: "Merchant", maxHp: 9999, armor: 0, atk: 0);
|
||||
|
||||
_addLog("Stage $stage: Entered a Shop.");
|
||||
} else if (type == StageType.rest) {
|
||||
// Dummy enemy
|
||||
enemy = Character(name: "Campfire", maxHp: 9999, armor: 0, atk: 0);
|
||||
_addLog("Stage $stage: Found a safe resting spot.");
|
||||
}
|
||||
|
||||
currentStage = StageModel(
|
||||
type: type,
|
||||
enemy: newEnemy,
|
||||
shopItems: shopItems,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Replaces _spawnEnemy
|
||||
// void _spawnEnemy() { ... } - Removed
|
||||
|
||||
/// Handle player's action choice
|
||||
|
||||
void playerAction(ActionType type, RiskLevel risk) {
|
||||
if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup) return;
|
||||
if (!isPlayerTurn || player.isDead || enemy.isDead || showRewardPopup)
|
||||
return;
|
||||
|
||||
// Update Enemy Status Effects at the start of Player's turn (user request)
|
||||
|
||||
enemy.updateStatusEffects();
|
||||
|
||||
// 1. Check for Defense Forbidden status
|
||||
if (type == ActionType.defend &&
|
||||
player.hasStatus(StatusEffectType.defenseForbidden)) {
|
||||
_addLog("Cannot defend! You are under Defense Forbidden status.");
|
||||
return;
|
||||
}
|
||||
|
||||
isPlayerTurn = false;
|
||||
notifyListeners();
|
||||
|
||||
// 2. Process Start-of-Turn Effects (Stun, Bleed)
|
||||
bool canAct = _processStartTurnEffects(player);
|
||||
if (!canAct) {
|
||||
_endPlayerTurn(); // Skip turn if stunned
|
||||
return;
|
||||
}
|
||||
|
||||
_addLog("Player chose to ${type.name} with ${risk.name} risk.");
|
||||
|
||||
final random = Random();
|
||||
@@ -91,8 +215,11 @@ class BattleProvider with ChangeNotifier {
|
||||
int damage = (player.totalAtk * efficiency).toInt();
|
||||
_applyDamage(enemy, damage);
|
||||
_addLog("Player dealt $damage damage to Enemy.");
|
||||
|
||||
// Try applying status effects from items
|
||||
_tryApplyStatusEffects(player, enemy);
|
||||
} else {
|
||||
int armorGained = (player.totalDefense * efficiency).toInt(); // Changed to totalDefense
|
||||
int armorGained = (player.totalDefense * efficiency).toInt();
|
||||
player.armor += armorGained;
|
||||
_addLog("Player gained $armorGained armor.");
|
||||
}
|
||||
@@ -105,40 +232,70 @@ class BattleProvider with ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
_endPlayerTurn();
|
||||
}
|
||||
|
||||
void _endPlayerTurn() {
|
||||
// Update durations at end of turn
|
||||
player.updateStatusEffects();
|
||||
|
||||
// Check if enemy is dead from bleed
|
||||
if (enemy.isDead) {
|
||||
_onVictory();
|
||||
return;
|
||||
}
|
||||
|
||||
Future.delayed(const Duration(seconds: 1), () => _enemyTurn());
|
||||
}
|
||||
|
||||
Future<void> _enemyTurn() async {
|
||||
if (!isPlayerTurn && (player.isDead || enemy.isDead)) return; // Check if it's the enemy's turn and battle is over
|
||||
if (!isPlayerTurn && (player.isDead || enemy.isDead)) return;
|
||||
|
||||
_addLog("Enemy's turn...");
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
|
||||
// Enemy attacks player
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulating thinking time
|
||||
// 1. Process Start-of-Turn Effects for Enemy
|
||||
bool canAct = _processStartTurnEffects(enemy);
|
||||
|
||||
int incomingDamage = enemy.totalAtk;
|
||||
int damageToHp = 0;
|
||||
// Check death from bleed before acting
|
||||
if (enemy.isDead) {
|
||||
_onVictory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.armor > 0) {
|
||||
if (player.armor >= incomingDamage) {
|
||||
player.armor -= incomingDamage;
|
||||
damageToHp = 0;
|
||||
_addLog("Armor absorbed all $incomingDamage damage.");
|
||||
if (canAct) {
|
||||
int incomingDamage = enemy.totalAtk;
|
||||
int damageToHp = 0;
|
||||
|
||||
// Enemy attack logic
|
||||
// (Simple logic: Enemy always attacks for now)
|
||||
// Note: Enemy doesn't have equipment yet, so no effects applied by enemy.
|
||||
|
||||
// Handle Player Armor
|
||||
if (player.armor > 0) {
|
||||
if (player.armor >= incomingDamage) {
|
||||
player.armor -= incomingDamage;
|
||||
damageToHp = 0;
|
||||
_addLog("Armor absorbed all $incomingDamage damage.");
|
||||
} else {
|
||||
damageToHp = incomingDamage - player.armor;
|
||||
_addLog("Armor absorbed ${player.armor} damage.");
|
||||
player.armor = 0;
|
||||
}
|
||||
} else {
|
||||
damageToHp = incomingDamage - player.armor;
|
||||
_addLog("Armor absorbed ${player.armor} damage.");
|
||||
player.armor = 0;
|
||||
damageToHp = incomingDamage;
|
||||
}
|
||||
|
||||
if (damageToHp > 0) {
|
||||
_applyDamage(player, damageToHp);
|
||||
_addLog("Enemy dealt $damageToHp damage to Player HP.");
|
||||
}
|
||||
} else {
|
||||
damageToHp = incomingDamage;
|
||||
_addLog("Enemy is stunned and cannot act!");
|
||||
}
|
||||
|
||||
if (damageToHp > 0) {
|
||||
_applyDamage(player, damageToHp);
|
||||
_addLog("Enemy dealt $damageToHp damage to Player HP.");
|
||||
}
|
||||
|
||||
// Player's turn starts, armor decays
|
||||
// Player Turn Start Logic
|
||||
// Armor decay
|
||||
if (player.armor > 0) {
|
||||
player.armor = (player.armor * 0.5).toInt();
|
||||
_addLog("Player's armor decayed to ${player.armor}.");
|
||||
@@ -152,7 +309,59 @@ class BattleProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Process effects that happen at the start of the turn (Bleed, Stun).
|
||||
/// Returns true if the character can act, false if stunned.
|
||||
bool _processStartTurnEffects(Character character) {
|
||||
bool canAct = true;
|
||||
|
||||
// 1. Bleed Damage
|
||||
var bleedEffects = character.statusEffects
|
||||
.where((e) => e.type == StatusEffectType.bleed)
|
||||
.toList();
|
||||
if (bleedEffects.isNotEmpty) {
|
||||
int totalBleed = bleedEffects.fold(0, (sum, e) => sum + e.value);
|
||||
character.hp -= totalBleed;
|
||||
if (character.hp < 0) character.hp = 0;
|
||||
_addLog("${character.name} takes $totalBleed bleed damage!");
|
||||
}
|
||||
|
||||
// 2. Stun Check
|
||||
if (character.hasStatus(StatusEffectType.stun)) {
|
||||
canAct = false;
|
||||
_addLog("${character.name} is stunned!");
|
||||
}
|
||||
|
||||
return canAct;
|
||||
}
|
||||
|
||||
/// Tries to apply status effects from attacker's equipment to the target.
|
||||
void _tryApplyStatusEffects(Character attacker, Character target) {
|
||||
final random = Random();
|
||||
|
||||
for (var item in attacker.equipment.values) {
|
||||
for (var effect in item.effects) {
|
||||
// Roll for probability (0-100)
|
||||
if (random.nextInt(100) < effect.probability) {
|
||||
// Apply effect
|
||||
final newStatus = StatusEffect(
|
||||
type: effect.type,
|
||||
duration: effect.duration,
|
||||
value: effect.value,
|
||||
);
|
||||
target.addStatusEffect(newStatus);
|
||||
_addLog("Applied ${effect.type.name} to ${target.name}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _applyDamage(Character target, int damage) {
|
||||
// Check Vulnerable
|
||||
if (target.hasStatus(StatusEffectType.vulnerable)) {
|
||||
damage = (damage * 1.5).toInt();
|
||||
_addLog("Vulnerable! Damage increased to $damage.");
|
||||
}
|
||||
|
||||
target.hp -= damage;
|
||||
if (target.hp < 0) target.hp = 0;
|
||||
}
|
||||
@@ -164,7 +373,7 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
void _onVictory() {
|
||||
_addLog("Enemy defeated! Choose a reward.");
|
||||
|
||||
|
||||
final random = Random();
|
||||
List<ItemTemplate> allTemplates = List.from(ItemTable.allItems);
|
||||
allTemplates.shuffle(random); // Shuffle to randomize selection
|
||||
@@ -186,7 +395,7 @@ class BattleProvider with ChangeNotifier {
|
||||
} else {
|
||||
_addLog("Inventory is full! ${item.name} discarded.");
|
||||
}
|
||||
|
||||
|
||||
// Heal player after selecting reward
|
||||
int healAmount = GameMath.floor(player.totalMaxHp * 0.5);
|
||||
player.heal(healAmount);
|
||||
@@ -194,11 +403,12 @@ class BattleProvider with ChangeNotifier {
|
||||
|
||||
stage++;
|
||||
showRewardPopup = false;
|
||||
|
||||
_spawnEnemy();
|
||||
_addLog("Stage $stage started! A wild ${enemy.name} appeared.");
|
||||
|
||||
isPlayerTurn = true;
|
||||
|
||||
_prepareNextStage();
|
||||
|
||||
// Log moved to _prepareNextStage
|
||||
|
||||
// isPlayerTurn = true; // Handled in _prepareNextStage for battles
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -206,7 +416,9 @@ class BattleProvider with ChangeNotifier {
|
||||
if (player.equip(item)) {
|
||||
_addLog("Equipped ${item.name}.");
|
||||
} else {
|
||||
_addLog("Failed to equip ${item.name}."); // Should not happen if logic is correct
|
||||
_addLog(
|
||||
"Failed to equip ${item.name}.",
|
||||
); // Should not happen if logic is correct
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -219,4 +431,25 @@ class BattleProvider with ChangeNotifier {
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void discardItem(Item item) {
|
||||
if (player.inventory.remove(item)) {
|
||||
_addLog("Discarded ${item.name}.");
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void sellItem(Item item) {
|
||||
if (player.inventory.remove(item)) {
|
||||
player.gold += item.price;
|
||||
_addLog("Sold ${item.name} for ${item.price} G.");
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Proceed to next stage from non-battle stages (Shop, Rest)
|
||||
void proceedToNextStage() {
|
||||
stage++;
|
||||
_prepareNextStage();
|
||||
}
|
||||
}
|
||||
|
||||
+101
-10
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_test/game/model/item.dart';
|
||||
import 'package:game_test/game/model/stage.dart'; // Import StageModel
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/battle_provider.dart';
|
||||
import '../game/model/entity.dart';
|
||||
@@ -19,7 +20,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
super.initState();
|
||||
// Scroll to the bottom of the log when new messages are added
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,8 +123,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Consumer<BattleProvider>(
|
||||
builder: (context, provider, child) =>
|
||||
Text("Colosseum's Choice - Stage ${provider.stage}"),
|
||||
builder: (context, provider, child) => Text(
|
||||
"Colosseum - Stage ${provider.stage} (${provider.currentStage.type.name.toUpperCase()})",
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -132,6 +136,49 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
),
|
||||
body: Consumer<BattleProvider>(
|
||||
builder: (context, battleProvider, child) {
|
||||
// UI Switching based on Stage Type
|
||||
if (battleProvider.currentStage.type == StageType.shop) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.store, size: 64, color: Colors.amber),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Merchant Shop", style: TextStyle(fontSize: 24)),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Buying/Selling feature coming soon!"),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () => battleProvider.proceedToNextStage(),
|
||||
child: const Text("Leave Shop"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (battleProvider.currentStage.type == StageType.rest) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.local_hotel, size: 64, color: Colors.blue),
|
||||
const SizedBox(height: 16),
|
||||
const Text("Rest Area", style: TextStyle(fontSize: 24)),
|
||||
const SizedBox(height: 8),
|
||||
const Text("Take a breath and heal."),
|
||||
const SizedBox(height: 32),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
battleProvider.player.heal(20); // Simple heal
|
||||
battleProvider.proceedToNextStage();
|
||||
},
|
||||
child: const Text("Rest & Leave (+20 HP)"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Default: Battle UI (for Battle and Elite)
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
@@ -251,14 +298,30 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
if (item.hpBonus > 0) stats.add("+${item.hpBonus} HP");
|
||||
if (item.armorBonus > 0) stats.add("+${item.armorBonus} DEF");
|
||||
|
||||
if (stats.isEmpty) return const SizedBox.shrink(); // Hide if no stats
|
||||
List<String> effectTexts = item.effects.map((e) => e.description).toList();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.blueAccent),
|
||||
),
|
||||
if (stats.isEmpty && effectTexts.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.blueAccent),
|
||||
),
|
||||
),
|
||||
if (effectTexts.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
effectTexts.join(", "),
|
||||
style: const TextStyle(fontSize: 11, color: Colors.orangeAccent),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -282,6 +345,34 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
backgroundColor: Colors.grey,
|
||||
),
|
||||
),
|
||||
// Display Active Status Effects
|
||||
if (character.statusEffects.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
children: character.statusEffects.map((effect) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.deepOrange,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
"${effect.type.name.toUpperCase()} (${effect.duration})",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
if (!isEnemy) ...[
|
||||
Text("Armor: ${character.armor}"),
|
||||
Text("ATK: ${character.totalAtk}"),
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/battle_provider.dart';
|
||||
import 'main_wrapper.dart';
|
||||
|
||||
class CharacterSelectionScreen extends StatelessWidget {
|
||||
const CharacterSelectionScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Choose Your Hero"),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// Initialize Game
|
||||
context.read<BattleProvider>().initializeBattle();
|
||||
|
||||
// Navigate to Game Screen (MainWrapper)
|
||||
// Using pushReplacement to prevent going back to selection
|
||||
Navigator.pushAndRemoveUntil(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainWrapper()),
|
||||
(route) => false,
|
||||
);
|
||||
},
|
||||
child: Card(
|
||||
color: Colors.blueGrey[800],
|
||||
elevation: 8,
|
||||
child: Container(
|
||||
width: 300,
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.shield, size: 80, color: Colors.blue),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Warrior",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
"A balanced fighter with a sword and shield. Great for beginners.",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text("HP: 100", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text("ATK: 10", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text("DEF: 5", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
|
||||
import '../providers/battle_provider.dart';
|
||||
import '../game/model/item.dart';
|
||||
import '../game/model/entity.dart';
|
||||
import '../game/model/stage.dart'; // Import StageModel
|
||||
|
||||
class InventoryScreen extends StatelessWidget {
|
||||
const InventoryScreen({super.key});
|
||||
@@ -40,7 +41,8 @@ class InventoryScreen extends StatelessWidget {
|
||||
),
|
||||
_buildStatItem("ATK", "${player.totalAtk}"),
|
||||
_buildStatItem("DEF", "${player.totalDefense}"),
|
||||
_buildStatItem("Shield", "${player.armor}"), // Temporary armor points
|
||||
_buildStatItem("Shield", "${player.armor}"),
|
||||
_buildStatItem("Gold", "${player.gold} G", color: Colors.amber),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -154,12 +156,8 @@ class InventoryScreen extends StatelessWidget {
|
||||
final item = player.inventory[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// Show confirmation dialog before equipping
|
||||
_showEquipConfirmationDialog(
|
||||
context,
|
||||
battleProvider,
|
||||
item,
|
||||
);
|
||||
// Show Action Dialog instead of direct Equip
|
||||
_showItemActionDialog(context, battleProvider, item);
|
||||
},
|
||||
child: Card(
|
||||
color: Colors.blueGrey[700],
|
||||
@@ -216,18 +214,143 @@ class InventoryScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildStatItem(String label, String value) {
|
||||
Widget _buildStatItem(String label, String value, {Color? color}) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Shows a menu with actions for the selected item (Equip, Discard, etc.)
|
||||
void _showItemActionDialog(
|
||||
BuildContext context, BattleProvider provider, Item item) {
|
||||
|
||||
bool isShop = provider.currentStage.type == StageType.shop;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => SimpleDialog(
|
||||
title: Text("${item.name} Actions"),
|
||||
children: [
|
||||
SimpleDialogOption(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_showEquipConfirmationDialog(context, provider, item);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.shield, color: Colors.blue),
|
||||
SizedBox(width: 10),
|
||||
Text("Equip"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isShop)
|
||||
SimpleDialogOption(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_showSellConfirmationDialog(context, provider, item);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.attach_money, color: Colors.amber),
|
||||
const SizedBox(width: 10),
|
||||
Text("Sell (${item.price} G)"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SimpleDialogOption(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_showDiscardConfirmationDialog(context, provider, item);
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.delete, color: Colors.red),
|
||||
SizedBox(width: 10),
|
||||
Text("Discard"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSellConfirmationDialog(
|
||||
BuildContext context,
|
||||
BattleProvider provider,
|
||||
Item item,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("Sell Item"),
|
||||
content: Text("Sell ${item.name} for ${item.price} G?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.amber),
|
||||
onPressed: () {
|
||||
provider.sellItem(item);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text("Sell", style: TextStyle(color: Colors.black)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDiscardConfirmationDialog(
|
||||
BuildContext context,
|
||||
BattleProvider provider,
|
||||
Item item,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("Discard Item"),
|
||||
content: Text("Are you sure you want to discard ${item.name}?"),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||
onPressed: () {
|
||||
provider.discardItem(item);
|
||||
Navigator.pop(ctx);
|
||||
},
|
||||
child: const Text("Discard"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showEquipConfirmationDialog(
|
||||
BuildContext context,
|
||||
BattleProvider provider,
|
||||
@@ -395,14 +518,32 @@ class InventoryScreen extends StatelessWidget {
|
||||
if (item.hpBonus > 0) stats.add("+${item.hpBonus} HP");
|
||||
if (item.armorBonus > 0) stats.add("+${item.armorBonus} DEF");
|
||||
|
||||
if (stats.isEmpty) return const SizedBox.shrink(); // Hide if no stats
|
||||
// Include effects
|
||||
List<String> effectTexts = item.effects.map((e) => e.description).toList();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(fontSize: 10, color: Colors.blueAccent),
|
||||
),
|
||||
if (stats.isEmpty && effectTexts.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (stats.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
stats.join(", "),
|
||||
style: const TextStyle(fontSize: 10, color: Colors.blueAccent),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (effectTexts.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: Text(
|
||||
effectTexts.join("\n"),
|
||||
style: const TextStyle(fontSize: 9, color: Colors.orangeAccent),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'character_selection_screen.dart';
|
||||
|
||||
class MainMenuScreen extends StatelessWidget {
|
||||
const MainMenuScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Colors.black, Colors.blueGrey[900]!],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.gavel, size: 100, color: Colors.amber),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"COLOSSEUM'S CHOICE",
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
"Rise as a Legend",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 60),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const CharacterSelectionScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 50, vertical: 15),
|
||||
backgroundColor: Colors.amber[700],
|
||||
foregroundColor: Colors.black,
|
||||
textStyle:
|
||||
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
child: const Text("START GAME"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user