update
This commit is contained in:
parent
457eed4d3e
commit
0e96aa4f7c
|
|
@ -4,31 +4,36 @@
|
||||||
"name": "Goblin",
|
"name": "Goblin",
|
||||||
"baseHp": 20,
|
"baseHp": 20,
|
||||||
"baseAtk": 5,
|
"baseAtk": 5,
|
||||||
"baseDefense": 0
|
"baseDefense": 0,
|
||||||
|
"image": "assets/images/enemies/goblin.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Slime",
|
"name": "Slime",
|
||||||
"baseHp": 30,
|
"baseHp": 30,
|
||||||
"baseAtk": 3,
|
"baseAtk": 3,
|
||||||
"baseDefense": 1
|
"baseDefense": 1,
|
||||||
|
"image": "assets/images/enemies/slime.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Wolf",
|
"name": "Wolf",
|
||||||
"baseHp": 25,
|
"baseHp": 25,
|
||||||
"baseAtk": 7,
|
"baseAtk": 7,
|
||||||
"baseDefense": 0
|
"baseDefense": 0,
|
||||||
|
"image": "assets/images/enemies/wolf.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Bandit",
|
"name": "Bandit",
|
||||||
"baseHp": 35,
|
"baseHp": 35,
|
||||||
"baseAtk": 6,
|
"baseAtk": 6,
|
||||||
"baseDefense": 1
|
"baseDefense": 1,
|
||||||
|
"image": "assets/images/enemies/bandit.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Skeleton",
|
"name": "Skeleton",
|
||||||
"baseHp": 15,
|
"baseHp": 15,
|
||||||
"baseAtk": 8,
|
"baseAtk": 8,
|
||||||
"baseDefense": 0
|
"baseDefense": 0,
|
||||||
|
"image": "assets/images/enemies/skeleton.png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"elite": [
|
"elite": [
|
||||||
|
|
@ -36,19 +41,22 @@
|
||||||
"name": "Orc Warrior",
|
"name": "Orc Warrior",
|
||||||
"baseHp": 60,
|
"baseHp": 60,
|
||||||
"baseAtk": 12,
|
"baseAtk": 12,
|
||||||
"baseDefense": 3
|
"baseDefense": 3,
|
||||||
|
"image": "assets/images/enemies/orc_warrior.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Giant Spider",
|
"name": "Giant Spider",
|
||||||
"baseHp": 50,
|
"baseHp": 50,
|
||||||
"baseAtk": 15,
|
"baseAtk": 15,
|
||||||
"baseDefense": 2
|
"baseDefense": 2,
|
||||||
|
"image": "assets/images/enemies/giant_spider.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Dark Knight",
|
"name": "Dark Knight",
|
||||||
"baseHp": 80,
|
"baseHp": 80,
|
||||||
"baseAtk": 10,
|
"baseAtk": 10,
|
||||||
"baseDefense": 5
|
"baseDefense": 5,
|
||||||
|
"image": "assets/images/enemies/dark_knight.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,33 @@
|
||||||
"name": "Rusty Dagger",
|
"name": "Rusty Dagger",
|
||||||
"description": "Old and rusty, but better than nothing.",
|
"description": "Old and rusty, but better than nothing.",
|
||||||
"baseAtk": 3,
|
"baseAtk": 3,
|
||||||
"slot": "weapon"
|
"slot": "weapon",
|
||||||
|
"price": 30,
|
||||||
|
"image": "assets/images/items/rusty_dagger.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Iron Sword",
|
"name": "Iron Sword",
|
||||||
"description": "A standard soldier's sword.",
|
"description": "A standard soldier's sword.",
|
||||||
"baseAtk": 8,
|
"baseAtk": 8,
|
||||||
"slot": "weapon"
|
"slot": "weapon",
|
||||||
|
"price": 80,
|
||||||
|
"image": "assets/images/items/iron_sword.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Battle Axe",
|
"name": "Battle Axe",
|
||||||
"description": "Heavy but powerful.",
|
"description": "Heavy but powerful.",
|
||||||
"baseAtk": 12,
|
"baseAtk": 12,
|
||||||
"slot": "weapon"
|
"slot": "weapon",
|
||||||
|
"price": 120,
|
||||||
|
"image": "assets/images/items/battle_axe.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Stunning Hammer",
|
"name": "Stunning Hammer",
|
||||||
"description": "A heavy hammer that can stun foes.",
|
"description": "A heavy hammer that can stun foes.",
|
||||||
"baseAtk": 10,
|
"baseAtk": 10,
|
||||||
"slot": "weapon",
|
"slot": "weapon",
|
||||||
|
"price": 150,
|
||||||
|
"image": "assets/images/items/stunning_hammer.png",
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "stun",
|
"type": "stun",
|
||||||
|
|
@ -36,6 +44,8 @@
|
||||||
"description": "A cruel dagger that causes bleeding.",
|
"description": "A cruel dagger that causes bleeding.",
|
||||||
"baseAtk": 7,
|
"baseAtk": 7,
|
||||||
"slot": "weapon",
|
"slot": "weapon",
|
||||||
|
"price": 130,
|
||||||
|
"image": "assets/images/items/jagged_dagger.png",
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "bleed",
|
"type": "bleed",
|
||||||
|
|
@ -50,6 +60,8 @@
|
||||||
"description": "An axe that exposes enemy weaknesses.",
|
"description": "An axe that exposes enemy weaknesses.",
|
||||||
"baseAtk": 11,
|
"baseAtk": 11,
|
||||||
"slot": "weapon",
|
"slot": "weapon",
|
||||||
|
"price": 160,
|
||||||
|
"image": "assets/images/items/sunderer_axe.png",
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "vulnerable",
|
"type": "vulnerable",
|
||||||
|
|
@ -64,19 +76,25 @@
|
||||||
"name": "Torn Tunic",
|
"name": "Torn Tunic",
|
||||||
"description": "Offers minimal protection.",
|
"description": "Offers minimal protection.",
|
||||||
"baseHp": 10,
|
"baseHp": 10,
|
||||||
"slot": "armor"
|
"slot": "armor",
|
||||||
|
"price": 20,
|
||||||
|
"image": "assets/images/items/torn_tunic.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Leather Vest",
|
"name": "Leather Vest",
|
||||||
"description": "Light and flexible.",
|
"description": "Light and flexible.",
|
||||||
"baseHp": 30,
|
"baseHp": 30,
|
||||||
"slot": "armor"
|
"slot": "armor",
|
||||||
|
"price": 60,
|
||||||
|
"image": "assets/images/items/leather_vest.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Chainmail",
|
"name": "Chainmail",
|
||||||
"description": "Reliable protection against cuts.",
|
"description": "Reliable protection against cuts.",
|
||||||
"baseHp": 60,
|
"baseHp": 60,
|
||||||
"slot": "armor"
|
"slot": "armor",
|
||||||
|
"price": 120,
|
||||||
|
"image": "assets/images/items/chainmail.png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"shields": [
|
"shields": [
|
||||||
|
|
@ -84,25 +102,33 @@
|
||||||
"name": "Pot Lid",
|
"name": "Pot Lid",
|
||||||
"description": "It was used for cooking.",
|
"description": "It was used for cooking.",
|
||||||
"baseArmor": 1,
|
"baseArmor": 1,
|
||||||
"slot": "shield"
|
"slot": "shield",
|
||||||
|
"price": 10,
|
||||||
|
"image": "assets/images/items/pot_lid.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Wooden Shield",
|
"name": "Wooden Shield",
|
||||||
"description": "Sturdy oak wood.",
|
"description": "Sturdy oak wood.",
|
||||||
"baseArmor": 3,
|
"baseArmor": 3,
|
||||||
"slot": "shield"
|
"slot": "shield",
|
||||||
|
"price": 40,
|
||||||
|
"image": "assets/images/items/wooden_shield.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Kite Shield",
|
"name": "Kite Shield",
|
||||||
"description": "Used by knights.",
|
"description": "Used by knights.",
|
||||||
"baseArmor": 6,
|
"baseArmor": 6,
|
||||||
"slot": "shield"
|
"slot": "shield",
|
||||||
|
"price": 100,
|
||||||
|
"image": "assets/images/items/kite_shield.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Cursed Shield",
|
"name": "Cursed Shield",
|
||||||
"description": "A shield that prevents the wielder from defending themselves.",
|
"description": "A shield that prevents the wielder from defending themselves.",
|
||||||
"baseArmor": 5,
|
"baseArmor": 5,
|
||||||
"slot": "shield",
|
"slot": "shield",
|
||||||
|
"price": 50,
|
||||||
|
"image": "assets/images/items/cursed_shield.png",
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
"type": "defenseForbidden",
|
"type": "defenseForbidden",
|
||||||
|
|
@ -118,21 +144,27 @@
|
||||||
"description": "A tarnished ring.",
|
"description": "A tarnished ring.",
|
||||||
"baseAtk": 1,
|
"baseAtk": 1,
|
||||||
"baseHp": 5,
|
"baseHp": 5,
|
||||||
"slot": "accessory"
|
"slot": "accessory",
|
||||||
|
"price": 25,
|
||||||
|
"image": "assets/images/items/old_ring.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Copper Ring",
|
"name": "Copper Ring",
|
||||||
"description": "A simple ring",
|
"description": "A simple ring",
|
||||||
"baseAtk": 1,
|
"baseAtk": 1,
|
||||||
"baseHp": 5,
|
"baseHp": 5,
|
||||||
"slot": "accessory"
|
"slot": "accessory",
|
||||||
|
"price": 25,
|
||||||
|
"image": "assets/images/items/copper_ring.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Ruby Amulet",
|
"name": "Ruby Amulet",
|
||||||
"description": "Glows with a faint red light.",
|
"description": "Glows with a faint red light.",
|
||||||
"baseAtk": 3,
|
"baseAtk": 3,
|
||||||
"baseHp": 15,
|
"baseHp": 15,
|
||||||
"slot": "accessory"
|
"slot": "accessory",
|
||||||
|
"price": 80,
|
||||||
|
"image": "assets/images/items/ruby_amulet.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Hero's Badge",
|
"name": "Hero's Badge",
|
||||||
|
|
@ -140,7 +172,9 @@
|
||||||
"baseAtk": 5,
|
"baseAtk": 5,
|
||||||
"baseHp": 25,
|
"baseHp": 25,
|
||||||
"baseArmor": 1,
|
"baseArmor": 1,
|
||||||
"slot": "accessory"
|
"slot": "accessory",
|
||||||
|
"price": 150,
|
||||||
|
"image": "assets/images/items/heros_badge.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ class EnemyTemplate {
|
||||||
final int baseHp;
|
final int baseHp;
|
||||||
final int baseAtk;
|
final int baseAtk;
|
||||||
final int baseDefense;
|
final int baseDefense;
|
||||||
|
final String? image;
|
||||||
|
|
||||||
const EnemyTemplate({
|
const EnemyTemplate({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.baseHp,
|
required this.baseHp,
|
||||||
required this.baseAtk,
|
required this.baseAtk,
|
||||||
required this.baseDefense,
|
required this.baseDefense,
|
||||||
|
this.image,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory EnemyTemplate.fromJson(Map<String, dynamic> json) {
|
factory EnemyTemplate.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -21,6 +23,7 @@ class EnemyTemplate {
|
||||||
baseHp: json['baseHp'] ?? 10,
|
baseHp: json['baseHp'] ?? 10,
|
||||||
baseAtk: json['baseAtk'] ?? 1,
|
baseAtk: json['baseAtk'] ?? 1,
|
||||||
baseDefense: json['baseDefense'] ?? 0,
|
baseDefense: json['baseDefense'] ?? 0,
|
||||||
|
image: json['image'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +39,7 @@ class EnemyTemplate {
|
||||||
atk: scaledAtk,
|
atk: scaledAtk,
|
||||||
baseDefense: scaledDefense,
|
baseDefense: scaledDefense,
|
||||||
armor: 0,
|
armor: 0,
|
||||||
|
image: image,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ class ItemTemplate {
|
||||||
final int baseArmor;
|
final int baseArmor;
|
||||||
final EquipmentSlot slot;
|
final EquipmentSlot slot;
|
||||||
final List<ItemEffect> effects;
|
final List<ItemEffect> effects;
|
||||||
|
final int price;
|
||||||
|
final String? image;
|
||||||
|
|
||||||
const ItemTemplate({
|
const ItemTemplate({
|
||||||
required this.name,
|
required this.name,
|
||||||
|
|
@ -20,6 +22,8 @@ class ItemTemplate {
|
||||||
this.baseArmor = 0,
|
this.baseArmor = 0,
|
||||||
required this.slot,
|
required this.slot,
|
||||||
this.effects = const [],
|
this.effects = const [],
|
||||||
|
this.price = 0,
|
||||||
|
this.image,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ItemTemplate.fromJson(Map<String, dynamic> json) {
|
factory ItemTemplate.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -30,10 +34,13 @@ class ItemTemplate {
|
||||||
baseHp: json['baseHp'] ?? 0,
|
baseHp: json['baseHp'] ?? 0,
|
||||||
baseArmor: json['baseArmor'] ?? 0,
|
baseArmor: json['baseArmor'] ?? 0,
|
||||||
slot: EquipmentSlot.values.firstWhere((e) => e.name == json['slot']),
|
slot: EquipmentSlot.values.firstWhere((e) => e.name == json['slot']),
|
||||||
effects: (json['effects'] as List<dynamic>?)
|
effects:
|
||||||
|
(json['effects'] as List<dynamic>?)
|
||||||
?.map((e) => ItemEffect.fromJson(e))
|
?.map((e) => ItemEffect.fromJson(e))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
|
price: json['price'] ?? 0,
|
||||||
|
image: json['image'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,12 +52,12 @@ class ItemTemplate {
|
||||||
int scaledHp = baseHp > 0 ? baseHp + (stage - 1) * 5 : 0;
|
int scaledHp = baseHp > 0 ? baseHp + (stage - 1) * 5 : 0;
|
||||||
int scaledArmor = baseArmor > 0 ? baseArmor + (stage - 1) : 0;
|
int scaledArmor = baseArmor > 0 ? baseArmor + (stage - 1) : 0;
|
||||||
|
|
||||||
// Calculate price based on stats
|
// Use fixed price from template
|
||||||
int calculatedPrice = (scaledAtk * 10) + (scaledHp * 2) + (scaledArmor * 5);
|
int finalPrice = price;
|
||||||
if (effects.isNotEmpty) {
|
// Optional: Increase price if stage > 1 (e.g. +10% per stage)
|
||||||
calculatedPrice += effects.length * 50; // Bonus value for special effects
|
if (stage > 1) {
|
||||||
|
finalPrice = (price * (1 + (stage - 1) * 0.1)).toInt();
|
||||||
}
|
}
|
||||||
if (calculatedPrice < 10) calculatedPrice = 10; // Minimum price
|
|
||||||
|
|
||||||
return Item(
|
return Item(
|
||||||
name: "$name${stage > 1 ? ' +${stage - 1}' : ''}", // Append +1, +2 etc.
|
name: "$name${stage > 1 ? ' +${stage - 1}' : ''}", // Append +1, +2 etc.
|
||||||
|
|
@ -60,7 +67,8 @@ class ItemTemplate {
|
||||||
armorBonus: scaledArmor,
|
armorBonus: scaledArmor,
|
||||||
slot: slot,
|
slot: slot,
|
||||||
effects: effects, // Pass the effects to the Item
|
effects: effects, // Pass the effects to the Item
|
||||||
price: calculatedPrice,
|
price: finalPrice,
|
||||||
|
image: image,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,13 +80,23 @@ class ItemTable {
|
||||||
static List<ItemTemplate> accessories = [];
|
static List<ItemTemplate> accessories = [];
|
||||||
|
|
||||||
static Future<void> load() async {
|
static Future<void> load() async {
|
||||||
final String jsonString = await rootBundle.loadString('assets/data/items.json');
|
final String jsonString = await rootBundle.loadString(
|
||||||
|
'assets/data/items.json',
|
||||||
|
);
|
||||||
final Map<String, dynamic> data = jsonDecode(jsonString);
|
final Map<String, dynamic> data = jsonDecode(jsonString);
|
||||||
|
|
||||||
weapons = (data['weapons'] as List).map((e) => ItemTemplate.fromJson(e)).toList();
|
weapons = (data['weapons'] as List)
|
||||||
armors = (data['armors'] as List).map((e) => ItemTemplate.fromJson(e)).toList();
|
.map((e) => ItemTemplate.fromJson(e))
|
||||||
shields = (data['shields'] as List).map((e) => ItemTemplate.fromJson(e)).toList();
|
.toList();
|
||||||
accessories = (data['accessories'] 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 => [
|
static List<ItemTemplate> get allItems => [
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ class Character {
|
||||||
int armor; // Current temporary shield/armor points in battle
|
int armor; // Current temporary shield/armor points in battle
|
||||||
int baseAtk;
|
int baseAtk;
|
||||||
int baseDefense; // Base defense stat
|
int baseDefense; // Base defense stat
|
||||||
|
|
||||||
int gold; // New: Currency
|
int gold; // New: Currency
|
||||||
|
String? image; // New: Image path
|
||||||
Map<EquipmentSlot, Item> equipment = {};
|
Map<EquipmentSlot, Item> equipment = {};
|
||||||
List<Item> inventory = [];
|
List<Item> inventory = [];
|
||||||
final int maxInventorySize = 16;
|
final int maxInventorySize = 16;
|
||||||
|
|
@ -24,9 +26,10 @@ class Character {
|
||||||
required int atk,
|
required int atk,
|
||||||
this.baseDefense = 0,
|
this.baseDefense = 0,
|
||||||
this.gold = 0,
|
this.gold = 0,
|
||||||
}) : baseMaxHp = maxHp,
|
this.image,
|
||||||
baseAtk = atk,
|
}) : baseMaxHp = maxHp,
|
||||||
hp = hp ?? maxHp;
|
baseAtk = atk,
|
||||||
|
hp = hp ?? maxHp;
|
||||||
|
|
||||||
/// Adds a status effect. If it already exists, it refreshes duration or stacks based on logic.
|
/// 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.
|
/// For now, we'll implement a simple refresh/overwrite logic.
|
||||||
|
|
@ -99,7 +102,9 @@ class Character {
|
||||||
if (!inventory.contains(newItem)) return false;
|
if (!inventory.contains(newItem)) return false;
|
||||||
|
|
||||||
// 1. Calculate current HP ratio before any changes
|
// 1. Calculate current HP ratio before any changes
|
||||||
double hpRatio = totalMaxHp > 0 ? hp / totalMaxHp : 0.0; // Avoid division by zero
|
double hpRatio = totalMaxHp > 0
|
||||||
|
? hp / totalMaxHp
|
||||||
|
: 0.0; // Avoid division by zero
|
||||||
|
|
||||||
// 2. Handle Swap: If slot is occupied, unequip the old item first
|
// 2. Handle Swap: If slot is occupied, unequip the old item first
|
||||||
if (equipment.containsKey(newItem.slot)) {
|
if (equipment.containsKey(newItem.slot)) {
|
||||||
|
|
@ -116,9 +121,10 @@ class Character {
|
||||||
hp = (totalMaxHp * hpRatio).toInt();
|
hp = (totalMaxHp * hpRatio).toInt();
|
||||||
if (hp < 0) hp = 0; // Ensure HP does not go below zero
|
if (hp < 0) hp = 0; // Ensure HP does not go below zero
|
||||||
if (hp > totalMaxHp) {
|
if (hp > totalMaxHp) {
|
||||||
hp = totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
|
hp =
|
||||||
|
totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +134,9 @@ class Character {
|
||||||
if (!equipment.containsValue(item)) return false;
|
if (!equipment.containsValue(item)) return false;
|
||||||
|
|
||||||
// 1. Calculate current HP ratio before any changes
|
// 1. Calculate current HP ratio before any changes
|
||||||
double hpRatio = totalMaxHp > 0 ? hp / totalMaxHp : 0.0; // Avoid division by zero
|
double hpRatio = totalMaxHp > 0
|
||||||
|
? hp / totalMaxHp
|
||||||
|
: 0.0; // Avoid division by zero
|
||||||
|
|
||||||
if (inventory.length < maxInventorySize) {
|
if (inventory.length < maxInventorySize) {
|
||||||
equipment.remove(item.slot);
|
equipment.remove(item.slot);
|
||||||
|
|
@ -138,11 +146,12 @@ class Character {
|
||||||
hp = (totalMaxHp * hpRatio).toInt();
|
hp = (totalMaxHp * hpRatio).toInt();
|
||||||
if (hp < 0) hp = 0; // Ensure HP does not go below zero
|
if (hp < 0) hp = 0; // Ensure HP does not go below zero
|
||||||
if (hp > totalMaxHp) {
|
if (hp > totalMaxHp) {
|
||||||
hp = totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
|
hp =
|
||||||
|
totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class Item {
|
||||||
final EquipmentSlot slot;
|
final EquipmentSlot slot;
|
||||||
final List<ItemEffect> effects; // Status effects this item can inflict
|
final List<ItemEffect> effects; // Status effects this item can inflict
|
||||||
final int price; // New: Sell/Buy value
|
final int price; // New: Sell/Buy value
|
||||||
|
final String? image; // New: Image path
|
||||||
|
|
||||||
Item({
|
Item({
|
||||||
required this.name,
|
required this.name,
|
||||||
|
|
@ -56,6 +57,7 @@ class Item {
|
||||||
required this.slot,
|
required this.slot,
|
||||||
this.effects = const [], // Default to no effects
|
this.effects = const [], // Default to no effects
|
||||||
this.price = 0,
|
this.price = 0,
|
||||||
|
this.image,
|
||||||
});
|
});
|
||||||
|
|
||||||
String get typeName {
|
String get typeName {
|
||||||
|
|
@ -70,4 +72,4 @@ class Item {
|
||||||
return "Accessory";
|
return "Accessory";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -515,8 +515,9 @@ class BattleProvider with ChangeNotifier {
|
||||||
|
|
||||||
void sellItem(Item item) {
|
void sellItem(Item item) {
|
||||||
if (player.inventory.remove(item)) {
|
if (player.inventory.remove(item)) {
|
||||||
player.gold += item.price;
|
int sellPrice = GameMath.floor(item.price * 0.6);
|
||||||
_addLog("Sold ${item.name} for ${item.price} G.");
|
player.gold += sellPrice;
|
||||||
|
_addLog("Sold ${item.name} for $sellPrice G.");
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,70 +3,83 @@
|
||||||
이 파일은 다른 개발 환경이나 새로운 AI 세션에서 프로젝트의 현재 상태를 빠르게 파악하고 작업을 이어가기 위해 작성되었습니다.
|
이 파일은 다른 개발 환경이나 새로운 AI 세션에서 프로젝트의 현재 상태를 빠르게 파악하고 작업을 이어가기 위해 작성되었습니다.
|
||||||
|
|
||||||
## 1. 프로젝트 개요
|
## 1. 프로젝트 개요
|
||||||
* **프로젝트명:** Colosseum's Choice
|
|
||||||
* **플랫폼:** Flutter (Android/iOS/Web/Desktop)
|
- **프로젝트명:** Colosseum's Choice
|
||||||
* **장르:** 텍스트 기반의 턴제 RPG + GUI (로그라이크 요소 포함)
|
- **플랫폼:** Flutter (Android/iOS/Web/Desktop)
|
||||||
* **상태:** 프로토타입 단계 (핵심 전투 및 루프 구현 완료)
|
- **장르:** 텍스트 기반의 턴제 RPG + GUI (로그라이크 요소 포함)
|
||||||
|
- **상태:** 프로토타입 단계 (핵심 전투, 아이템, 적 시스템 데이터화 완료)
|
||||||
|
|
||||||
## 2. 현재 구현된 핵심 기능 (Feature Status)
|
## 2. 현재 구현된 핵심 기능 (Feature Status)
|
||||||
|
|
||||||
### A. 게임 흐름 (Game Flow)
|
### A. 게임 흐름 (Game Flow)
|
||||||
|
|
||||||
1. **메인 메뉴 (`MainMenuScreen`):** 게임 시작 버튼.
|
1. **메인 메뉴 (`MainMenuScreen`):** 게임 시작 버튼.
|
||||||
2. **캐릭터 선택 (`CharacterSelectionScreen`):** 현재 'Warrior' 직업만 구현됨. 선택 시 게임 초기화.
|
2. **캐릭터 선택 (`CharacterSelectionScreen`):** 현재 'Warrior' 직업만 구현됨. 선택 시 게임 초기화.
|
||||||
3. **메인 게임 (`MainWrapper`):** 하단 탭 네비게이션 (Battle / Inventory).
|
3. **메인 게임 (`MainWrapper`):** 하단 탭 네비게이션 (Battle / Inventory).
|
||||||
|
|
||||||
### B. 전투 시스템 (`BattleProvider`)
|
### B. 전투 시스템 (`BattleProvider`)
|
||||||
* **턴제 전투:** 플레이어 턴 -> 적 턴.
|
|
||||||
* **행동 선택:** 공격(Attack) / 방어(Defend).
|
|
||||||
* **리스크 시스템:** 행동 시 Safe(100% 성공, 50% 효율), Normal(80% 성공, 100% 효율), Risky(40% 성공, 200% 효율) 중 선택 가능.
|
|
||||||
* **상태이상 (Status Effects):**
|
|
||||||
* `Stun`: 행동 불가.
|
|
||||||
* `Bleed`: 턴 시작 시 지속 피해.
|
|
||||||
* `Vulnerable`: 받는 피해 1.5배 증가.
|
|
||||||
* `DefenseForbidden`: 방어 행동 불가.
|
|
||||||
* *특이사항:* 적에게 건 디버프는 플레이어 턴 시작 시 감소함 (직관성).
|
|
||||||
|
|
||||||
### C. 아이템 및 인벤토리 (`Item`, `ItemTable`)
|
- **턴제 전투:** 플레이어 턴 -> 적 턴.
|
||||||
* **장비:** 무기, 방어구, 방패, 장신구 슬롯.
|
- **행동 선택:** 공격(Attack) / 방어(Defend).
|
||||||
* **아이템 효과 (`ItemEffect`):** 아이템 공격 시 확률적으로 상태이상 부여 (예: 20% 확률로 기절).
|
- **리스크 시스템 (Risk System):**
|
||||||
* **경제:**
|
- 플레이어와 적 모두 **Safe / Normal / Risky** 중 하나를 선택하여 행동.
|
||||||
* `Gold` 시스템 구현됨.
|
- Safe: 100% 성공, 50% 효율.
|
||||||
* 아이템마다 스탯 기반 `price` 자동 산출.
|
- Normal: 80% 성공, 100% 효율.
|
||||||
* 상점 스테이지에서 **판매(Sell)** 기능 구현됨.
|
- Risky: 40% 성공, 200% 효율.
|
||||||
* 인벤토리에서 장착(Equip), 버리기(Discard), 판매(Sell) 가능.
|
- **적 인공지능 (Enemy AI & Intent):**
|
||||||
|
- 적은 턴 시작 시 행동(공격/방어)과 리스크 레벨을 무작위로 결정.
|
||||||
|
- **Intent UI:** 플레이어는 적의 다음 행동(아이콘, 설명)을 미리 볼 수 있음.
|
||||||
|
- _규칙:_ 적의 `baseDefense`가 0이면 방어 행동을 하지 않음.
|
||||||
|
- **상태이상 (Status Effects):**
|
||||||
|
- `Stun`, `Bleed`, `Vulnerable`, `DefenseForbidden` 구현됨.
|
||||||
|
|
||||||
### D. 스테이지 시스템 (`StageModel`, `StageType`)
|
### C. 데이터 주도 설계 (Data-Driven Design)
|
||||||
* **스테이지 진행:** `currentStage` 객체로 관리.
|
|
||||||
* **타입 분기:**
|
- **JSON 데이터 관리:** `assets/data/` 폴더 내 JSON 파일로 게임 데이터 관리.
|
||||||
* `Battle`: 일반 몬스터 전투.
|
- `items.json`: 아이템 정의 (이름, 스탯, 효과, **가격**, **이미지 경로**).
|
||||||
* `Shop`: (5, 15... 스테이지) 상점 화면. (현재 판매만 가능, 구매 UI 미구현)
|
- `enemies.json`: 적 정의 (Normal/Elite, 스탯, **이미지 경로**).
|
||||||
* `Rest`: (8, 18... 스테이지) 휴식 화면. (HP 회복)
|
- **데이터 로더:**
|
||||||
* `Elite`: (10, 20... 스테이지) 강력한 적 등장.
|
- `ItemTable`: `items.json` 로드 및 `ItemTemplate` 관리.
|
||||||
|
- `EnemyTable`: `enemies.json` 로드 및 `EnemyTemplate` 관리.
|
||||||
|
|
||||||
|
### D. 아이템 및 경제 (`Item`, `Inventory`)
|
||||||
|
|
||||||
|
- **장비:** 무기, 방어구, 방패, 장신구 슬롯.
|
||||||
|
- **가격 정책:**
|
||||||
|
- `items.json`에 정의된 고정 `price` 사용.
|
||||||
|
- **판매(Sell):** 상점 등에서 판매 시 원가의 **60%** (소수점 버림, `GameMath.floor`) 획득.
|
||||||
|
- **이미지 필드:** 향후 UI 사용을 위해 `Item` 및 `Enemy` 모델에 `image` 필드 추가됨.
|
||||||
|
|
||||||
|
### E. 스테이지 시스템 (`StageModel`, `StageType`)
|
||||||
|
|
||||||
|
- **진행:** `currentStage` 객체로 관리.
|
||||||
|
- **타입:** Battle, Shop (5단위), Rest (8단위), Elite (10단위).
|
||||||
|
- **적 생성:** `EnemyTable`에서 현재 스테이지 타입(Normal/Elite)에 맞는 적을 무작위로 스폰하며, 스테이지에 따라 스탯 스케일링 적용.
|
||||||
|
|
||||||
## 3. 핵심 파일 및 아키텍처
|
## 3. 핵심 파일 및 아키텍처
|
||||||
|
|
||||||
* **`lib/providers/battle_provider.dart`:** 게임의 **Core Logic**을 담당하는 거대 Provider. 상태 관리, 전투 계산, 스테이지 생성 등을 모두 처리.
|
- **`lib/providers/battle_provider.dart`:** 게임의 **Core Logic**. 상태 관리, 전투 루프, 적 AI(Intent) 생성, 스테이지 전환 담당.
|
||||||
* **`lib/game/model/`:**
|
- **`lib/game/data/`:**
|
||||||
* `entity.dart`: `Character` 클래스 (Player/Enemy 공용).
|
- `item_table.dart`: 아이템 JSON 로더.
|
||||||
* `item.dart`: `Item`, `ItemEffect`.
|
- `enemy_table.dart`: 적 JSON 로더.
|
||||||
* `stage.dart`: `StageModel`, `StageType`.
|
- **`lib/game/model/`:**
|
||||||
* `status_effect.dart`: 상태이상 정의.
|
- `entity.dart`: `Character` 클래스 (Player/Enemy 공용). `image` 필드 포함.
|
||||||
* **`lib/game/data/item_table.dart`:** 아이템 템플릿 데이터.
|
- `item.dart`: `Item` 클래스. `price`, `image` 필드 포함.
|
||||||
* **`lib/screens/`:**
|
- **`assets/data/`:** `items.json`, `enemies.json`.
|
||||||
* `battle_screen.dart`: 전투 및 스테이지 상황(상점/휴식) UI.
|
|
||||||
* `inventory_screen.dart`: 인벤토리 및 장비 관리 UI.
|
|
||||||
|
|
||||||
## 4. 작업 컨벤션 (Working Conventions)
|
## 4. 작업 컨벤션 (Working Conventions)
|
||||||
|
|
||||||
* **Prompt Driven Development:** 새로운 기능을 구현할 때마다 `prompt/XX_description.md` 파일을 생성하여 작업 목표와 내용을 기록한다.
|
- **Prompt Driven Development:** `prompt/XX_description.md` 형식을 유지하며 작업.
|
||||||
* **State Management:** `Provider` 패키지를 사용하며, 로직은 주로 `BattleProvider`에 집중시킨다.
|
- **State Management:** `Provider` 사용.
|
||||||
* **Data Preservation:** 하드코딩된 데이터(`ItemTable`)를 사용 중이며, DB는 연동되지 않음.
|
- **Data:** JSON 파일 기반의 데이터 관리.
|
||||||
|
|
||||||
## 5. 다음 단계 작업 (Next Steps)
|
## 5. 다음 단계 작업 (Next Steps)
|
||||||
|
|
||||||
1. **상점 구매 기능:** `Shop` 스테이지에서 아이템을 구매하는 UI 및 로직 구현.
|
1. **상점 구매 기능:** `Shop` 스테이지에서 아이템 목록을 보여주고 구매하는 UI 구현.
|
||||||
2. **밸런싱:** 상태이상 확률, 데미지 공식, 골드 획득량 조정.
|
2. **이미지 리소스 적용:** JSON에 정의된 경로에 실제 이미지 파일(`assets/images/...`)을 추가하고 UI(`BattleScreen`, `InventoryScreen`)에 표시.
|
||||||
3. **UI 개선:** 텍스트 위주의 로그를 시각적 효과(애니메이션, 플로팅 텍스트)로 개선.
|
3. **UI 개선:** 텍스트 로그 외에 시각적 피드백(데미지 플로팅, 효과 이펙트) 추가.
|
||||||
|
4. **밸런싱 및 콘텐츠 확장:** 더 많은 아이템과 적 데이터 추가.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**이 프롬프트를 읽은 AI 에이전트는 위 내용을 바탕으로 즉시 개발을 이어가십시오.**
|
**이 프롬프트를 읽은 AI 에이전트는 위 내용을 바탕으로 즉시 개발을 이어가십시오.**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
# 23. 아이템 가격 리팩토링 및 이미지 필드 추가
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
1. 아이템의 가격(`price`)을 코드 내 동적 계산이 아닌 `items.json`에 명시된 고정 값으로 변경합니다.
|
||||||
|
2. 상점 구매 시 이 고정 가격을 사용하고, 판매 시에는 60%의 가격을 적용합니다.
|
||||||
|
3. 향후 UI 개선을 위해 `items.json`과 `enemies.json`에 이미지 경로 필드를 미리 추가합니다.
|
||||||
|
|
||||||
|
## 1. 아이템 가격 리팩토링
|
||||||
|
|
||||||
|
### A. 데이터 파일 수정 (`assets/data/items.json`)
|
||||||
|
|
||||||
|
- 모든 아이템 항목에 `price` 필드를 추가합니다. (예: `"price": 100`)
|
||||||
|
- 적절한 가격을 설정합니다.
|
||||||
|
|
||||||
|
### B. 데이터 로더 수정 (`lib/game/data/item_table.dart`)
|
||||||
|
|
||||||
|
- `ItemTemplate` 클래스에서 `price` 필드를 파싱하도록 수정합니다.
|
||||||
|
- `createItem` 메서드에서 가격을 계산하는 로직을 제거하고, 템플릿의 `price`를 그대로 사용하도록 변경합니다.
|
||||||
|
- 단, 스테이지 스케일링에 따라 가격이 변동되어야 한다면 그 로직은 유지하거나 수정할 수 있습니다. (현재 요구사항은 "고정된 price"이므로 기본적으로 JSON 값을 따르되, +1 강화된 아이템의 경우 가격 상승 로직이 필요할 수 있음. 일단 기본 가격은 JSON을 따르게 함)
|
||||||
|
|
||||||
|
### C. 상점 및 판매 로직 확인
|
||||||
|
|
||||||
|
- `BattleProvider` (또는 상점 로직이 있는 곳)에서 아이템 구매 시 `item.price`를 사용하도록 확인합니다.
|
||||||
|
- 아이템 판매 시 `GameMath.floor(item.price * 0.6)`를 사용하여 소수점을 버리도록 로직을 수정합니다.
|
||||||
|
|
||||||
|
## 2. 이미지 필드 추가
|
||||||
|
|
||||||
|
### A. JSON 데이터 수정
|
||||||
|
|
||||||
|
- `assets/data/items.json`: 각 아이템에 `image` 필드 추가 (예: `"image": "assets/images/items/sword.png"`)
|
||||||
|
- `assets/data/enemies.json`: 각 적에게 `image` 필드 추가 (예: `"image": "assets/images/enemies/goblin.png"`)
|
||||||
|
- 실제 이미지 파일은 아직 없으므로 필드만 추가합니다.
|
||||||
|
|
||||||
|
### B. 모델 클래스 수정
|
||||||
|
|
||||||
|
- `lib/game/data/item_table.dart` -> `ItemTemplate`: `image` 필드 파싱 추가.
|
||||||
|
- `lib/game/model/item.dart` -> `Item`: `image` 필드 추가.
|
||||||
|
- `lib/game/data/enemy_table.dart` -> `EnemyTemplate`: `image` 필드 파싱 추가.
|
||||||
|
- `lib/game/model/entity.dart` -> `Character`: `image` 필드 추가 (선택적).
|
||||||
|
|
||||||
|
### C. UI 반영 (BattleScreen, Inventory)
|
||||||
|
|
||||||
|
- `BattleScreen`과 인벤토리 UI에서 해당 이미지 경로를 사용할 수 있도록 준비합니다.
|
||||||
|
- 이미지가 없는 경우(null 또는 파일 없음) 기존처럼 텍스트나 기본 아이콘을 표시하도록 예외 처리를 해둡니다.
|
||||||
|
|
||||||
|
## 검증
|
||||||
|
|
||||||
|
- `items.json`의 가격이 게임 내(상점/인벤토리)에 올바르게 반영되는지 확인.
|
||||||
|
- 판매 시 가격이 60%로 계산되는지 확인.
|
||||||
|
- 데이터 로딩 시 이미지 필드가 정상적으로 파싱되는지 확인 (테스트 코드 활용).
|
||||||
|
|
@ -17,6 +17,7 @@ void main() {
|
||||||
atkBonus: 0,
|
atkBonus: 0,
|
||||||
hpBonus: 50,
|
hpBonus: 50,
|
||||||
slot: EquipmentSlot.armor,
|
slot: EquipmentSlot.armor,
|
||||||
|
price: 100,
|
||||||
);
|
);
|
||||||
armorHp100 = Item(
|
armorHp100 = Item(
|
||||||
name: "Armor +100",
|
name: "Armor +100",
|
||||||
|
|
@ -24,6 +25,7 @@ void main() {
|
||||||
atkBonus: 0,
|
atkBonus: 0,
|
||||||
hpBonus: 100,
|
hpBonus: 100,
|
||||||
slot: EquipmentSlot.armor,
|
slot: EquipmentSlot.armor,
|
||||||
|
price: 200,
|
||||||
);
|
);
|
||||||
armorHp20 = Item(
|
armorHp20 = Item(
|
||||||
name: "Armor +20",
|
name: "Armor +20",
|
||||||
|
|
@ -31,63 +33,107 @@ void main() {
|
||||||
atkBonus: 0,
|
atkBonus: 0,
|
||||||
hpBonus: 20,
|
hpBonus: 20,
|
||||||
slot: EquipmentSlot.armor,
|
slot: EquipmentSlot.armor,
|
||||||
|
price: 50,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add items to inventory initially
|
// Add items to inventory initially
|
||||||
player.addToInventory(armorHp50);
|
player.addToInventory(armorHp50);
|
||||||
player.addToInventory(armorHp100);
|
player.addToInventory(armorHp100);
|
||||||
player.addToInventory(armorHp20);
|
player.addToInventory(armorHp20);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Equipping item increases MaxHP and scales Current HP proportionally', () {
|
test(
|
||||||
expect(player.hp, 100);
|
'Equipping item increases MaxHP and scales Current HP proportionally',
|
||||||
expect(player.totalMaxHp, 100);
|
() {
|
||||||
|
expect(player.hp, 100);
|
||||||
|
expect(player.totalMaxHp, 100);
|
||||||
|
|
||||||
player.equip(armorHp50); // From 100/100 (100% HP) to 150 MaxHP
|
player.equip(armorHp50); // From 100/100 (100% HP) to 150 MaxHP
|
||||||
|
|
||||||
expect(player.totalMaxHp, 150, reason: "Max HP should increase by 50");
|
expect(player.totalMaxHp, 150, reason: "Max HP should increase by 50");
|
||||||
expect(player.hp, 150, reason: "Current HP should scale to 100% of new MaxHP");
|
expect(
|
||||||
});
|
player.hp,
|
||||||
|
150,
|
||||||
|
reason: "Current HP should scale to 100% of new MaxHP",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('Unequipping item decreases MaxHP and scales Current HP proportionally', () {
|
test(
|
||||||
player.equip(armorHp50); // HP becomes 150/150
|
'Unequipping item decreases MaxHP and scales Current HP proportionally',
|
||||||
player.unequip(armorHp50); // MaxHP becomes 100
|
() {
|
||||||
|
player.equip(armorHp50); // HP becomes 150/150
|
||||||
|
player.unequip(armorHp50); // MaxHP becomes 100
|
||||||
|
|
||||||
expect(player.totalMaxHp, 100);
|
expect(player.totalMaxHp, 100);
|
||||||
expect(player.hp, 100, reason: "Current HP should scale to 100% of new MaxHP");
|
expect(
|
||||||
});
|
player.hp,
|
||||||
|
100,
|
||||||
|
reason: "Current HP should scale to 100% of new MaxHP",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('Unequipping item clamps Current HP if it exceeds new MaxHP (Already 100% HP)', () {
|
test(
|
||||||
player.equip(armorHp50); // HP becomes 150/150
|
'Unequipping item clamps Current HP if it exceeds new MaxHP (Already 100% HP)',
|
||||||
// No need to heal(50) as it's already 150/150.
|
() {
|
||||||
expect(player.hp, 150);
|
player.equip(armorHp50); // HP becomes 150/150
|
||||||
|
// No need to heal(50) as it's already 150/150.
|
||||||
|
expect(player.hp, 150);
|
||||||
|
|
||||||
player.unequip(armorHp50); // MaxHP 100
|
player.unequip(armorHp50); // MaxHP 100
|
||||||
|
|
||||||
expect(player.totalMaxHp, 100);
|
expect(player.totalMaxHp, 100);
|
||||||
expect(player.hp, 100, reason: "Current HP should scale to 100% of new MaxHP");
|
expect(
|
||||||
});
|
player.hp,
|
||||||
|
100,
|
||||||
|
reason: "Current HP should scale to 100% of new MaxHP",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('Swapping items handles HP correctly (Upgrade and maintain percentage)', () {
|
test(
|
||||||
player.equip(armorHp50); // HP 100/100 -> equip -> 150/150 (100%)
|
'Swapping items handles HP correctly (Upgrade and maintain percentage)',
|
||||||
player.hp = 75; // Set HP to 75/150 (50%)
|
() {
|
||||||
expect(player.hp, 75);
|
player.equip(armorHp50); // HP 100/100 -> equip -> 150/150 (100%)
|
||||||
expect(player.totalMaxHp, 150);
|
player.hp = 75; // Set HP to 75/150 (50%)
|
||||||
|
expect(player.hp, 75);
|
||||||
|
expect(player.totalMaxHp, 150);
|
||||||
|
|
||||||
player.equip(armorHp100); // Swap armorHp50 (HP+50) with armorHp100 (HP+100). New MaxHP is 200.
|
player.equip(
|
||||||
|
armorHp100,
|
||||||
|
); // Swap armorHp50 (HP+50) with armorHp100 (HP+100). New MaxHP is 200.
|
||||||
|
|
||||||
expect(player.totalMaxHp, 200);
|
expect(player.totalMaxHp, 200);
|
||||||
expect(player.hp, 100, reason: "HP should scale to 50% of new MaxHP (200 * 0.5 = 100)");
|
expect(
|
||||||
expect(player.inventory.contains(armorHp50), true, reason: "Old item returned to inventory");
|
player.hp,
|
||||||
});
|
100,
|
||||||
|
reason: "HP should scale to 50% of new MaxHP (200 * 0.5 = 100)",
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
player.inventory.contains(armorHp50),
|
||||||
|
true,
|
||||||
|
reason: "Old item returned to inventory",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test('Swapping items handles HP correctly (Downgrade causing clamp due to percentage)', () {
|
test(
|
||||||
player.equip(armorHp50); // HP 100/100 -> equip -> 150/150 (100%)
|
'Swapping items handles HP correctly (Downgrade causing clamp due to percentage)',
|
||||||
|
() {
|
||||||
player.equip(armorHp20); // Swap armorHp50 (HP+50) with armorHp20 (HP+20). New MaxHP is 120.
|
player.equip(armorHp50); // HP 100/100 -> equip -> 150/150 (100%)
|
||||||
|
|
||||||
expect(player.totalMaxHp, 120);
|
player.equip(
|
||||||
expect(player.hp, 120, reason: "HP should scale to 100% of new MaxHP (120 * 1.0 = 120)");
|
armorHp20,
|
||||||
});
|
); // Swap armorHp50 (HP+50) with armorHp20 (HP+20). New MaxHP is 120.
|
||||||
|
|
||||||
|
expect(player.totalMaxHp, 120);
|
||||||
|
expect(
|
||||||
|
player.hp,
|
||||||
|
120,
|
||||||
|
reason: "HP should scale to 100% of new MaxHP (120 * 1.0 = 120)",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue