feat(api): integrate equipment stats into combat damage system
Equipment-Combat Integration: - Update Stats damage formula from STR//2 to int(STR*0.75) for better scaling - Add spell_power system for magical weapons (staves, wands) - Add spell_power_bonus field to Stats model with spell_power property - Add spell_power field to Item model with is_magical_weapon() method - Update Character.get_effective_stats() to populate spell_power_bonus Combatant Model Updates: - Add weapon property fields (crit_chance, crit_multiplier, damage_type) - Add elemental weapon support (elemental_damage_type, physical_ratio, elemental_ratio) - Update serialization to handle new weapon properties DamageCalculator Refactoring: - Remove weapon_damage parameter from calculate_physical_damage() - Use attacker_stats.damage directly (includes weapon bonus) - Use attacker_stats.spell_power for magical damage calculations Combat Service Updates: - Extract weapon properties in _create_combatant_from_character() - Use stats.damage_bonus for enemy combatants from templates - Remove hardcoded _get_weapon_damage() method - Handle elemental weapons with split damage in _execute_attack() Item Generation Updates: - Add base_spell_power to BaseItemTemplate dataclass - Add ARCANE damage type to DamageType enum - Add magical weapon templates (wizard_staff, arcane_staff, wand, crystal_wand) Test Updates: - Update test_stats.py for new damage formula (0.75 scaling) - Update test_character.py for equipment bonus calculations - Update test_damage_calculator.py for new API signatures - Update test_combat_service.py mock fixture for equipped attribute Tests: 174 passing
This commit is contained in:
@@ -6,9 +6,11 @@ Handles physical, magical, and elemental damage with LUK stat integration
|
||||
for variance, critical hits, and accuracy.
|
||||
|
||||
Formulas:
|
||||
Physical: (Weapon_Base + STR * 0.75) * Variance * Crit_Mult - DEF
|
||||
Magical: (Ability_Base + INT * 0.75) * Variance * Crit_Mult - RES
|
||||
Elemental: Split between physical and magical components
|
||||
Physical: (effective_stats.damage + ability_power) * Variance * Crit_Mult - DEF
|
||||
where effective_stats.damage = int(STR * 0.75) + damage_bonus (from weapon)
|
||||
Magical: (effective_stats.spell_power + ability_power) * Variance * Crit_Mult - RES
|
||||
where effective_stats.spell_power = int(INT * 0.75) + spell_power_bonus (from staff/wand)
|
||||
Elemental: Split between physical and magical components using ratios
|
||||
|
||||
LUK Integration:
|
||||
- Miss reduction: 10% base - (LUK * 0.5%), hard cap at 5% miss
|
||||
@@ -275,7 +277,6 @@ class DamageCalculator:
|
||||
cls,
|
||||
attacker_stats: Stats,
|
||||
defender_stats: Stats,
|
||||
weapon_damage: int = 0,
|
||||
weapon_crit_chance: float = CombatConstants.DEFAULT_CRIT_CHANCE,
|
||||
weapon_crit_multiplier: float = CombatConstants.DEFAULT_CRIT_MULTIPLIER,
|
||||
ability_base_power: int = 0,
|
||||
@@ -286,13 +287,13 @@ class DamageCalculator:
|
||||
Calculate physical damage for a melee/ranged attack.
|
||||
|
||||
Formula:
|
||||
Base = Weapon_Base + Ability_Power + (STR * 0.75)
|
||||
Base = attacker_stats.damage + ability_base_power
|
||||
where attacker_stats.damage = int(STR * 0.75) + damage_bonus
|
||||
Damage = Base * Variance * Crit_Mult - DEF
|
||||
|
||||
Args:
|
||||
attacker_stats: Attacker's Stats (STR, LUK used)
|
||||
attacker_stats: Attacker's Stats (includes weapon damage via damage property)
|
||||
defender_stats: Defender's Stats (DEX, CON used)
|
||||
weapon_damage: Base damage from equipped weapon
|
||||
weapon_crit_chance: Crit chance from weapon (default 5%)
|
||||
weapon_crit_multiplier: Crit damage multiplier (default 2.0x)
|
||||
ability_base_power: Additional base power from ability
|
||||
@@ -317,9 +318,8 @@ class DamageCalculator:
|
||||
return result
|
||||
|
||||
# Step 2: Calculate base damage
|
||||
# Formula: weapon + ability + (STR * scaling_factor)
|
||||
str_bonus = attacker_stats.strength * CombatConstants.STAT_SCALING_FACTOR
|
||||
base_damage = weapon_damage + ability_base_power + str_bonus
|
||||
# attacker_stats.damage already includes: int(STR * 0.75) + damage_bonus (weapon)
|
||||
base_damage = attacker_stats.damage + ability_base_power
|
||||
|
||||
# Step 3: Apply variance
|
||||
variance = cls.calculate_variance(attacker_stats.luck)
|
||||
@@ -371,11 +371,12 @@ class DamageCalculator:
|
||||
LUK benefits all classes equally.
|
||||
|
||||
Formula:
|
||||
Base = Ability_Power + (INT * 0.75)
|
||||
Base = attacker_stats.spell_power + ability_base_power
|
||||
where attacker_stats.spell_power = int(INT * 0.75) + spell_power_bonus
|
||||
Damage = Base * Variance * Crit_Mult - RES
|
||||
|
||||
Args:
|
||||
attacker_stats: Attacker's Stats (INT, LUK used)
|
||||
attacker_stats: Attacker's Stats (includes staff/wand spell_power via spell_power property)
|
||||
defender_stats: Defender's Stats (DEX, WIS used)
|
||||
ability_base_power: Base power of the spell
|
||||
damage_type: Type of magical damage (fire, ice, etc.)
|
||||
@@ -402,9 +403,8 @@ class DamageCalculator:
|
||||
return result
|
||||
|
||||
# Step 2: Calculate base damage
|
||||
# Formula: ability + (INT * scaling_factor)
|
||||
int_bonus = attacker_stats.intelligence * CombatConstants.STAT_SCALING_FACTOR
|
||||
base_damage = ability_base_power + int_bonus
|
||||
# attacker_stats.spell_power already includes: int(INT * 0.75) + spell_power_bonus (staff/wand)
|
||||
base_damage = attacker_stats.spell_power + ability_base_power
|
||||
|
||||
# Step 3: Apply variance
|
||||
variance = cls.calculate_variance(attacker_stats.luck)
|
||||
@@ -442,7 +442,6 @@ class DamageCalculator:
|
||||
cls,
|
||||
attacker_stats: Stats,
|
||||
defender_stats: Stats,
|
||||
weapon_damage: int,
|
||||
weapon_crit_chance: float,
|
||||
weapon_crit_multiplier: float,
|
||||
physical_ratio: float,
|
||||
@@ -459,8 +458,8 @@ class DamageCalculator:
|
||||
calculated separately against DEF and RES respectively.
|
||||
|
||||
Formula:
|
||||
Physical = (Weapon * PHYS_RATIO + STR * 0.75 * PHYS_RATIO) - DEF
|
||||
Elemental = (Weapon * ELEM_RATIO + INT * 0.75 * ELEM_RATIO) - RES
|
||||
Physical = (attacker_stats.damage + ability_power) * PHYS_RATIO - DEF
|
||||
Elemental = (attacker_stats.spell_power + ability_power) * ELEM_RATIO - RES
|
||||
Total = Physical + Elemental
|
||||
|
||||
Recommended Split Ratios:
|
||||
@@ -470,9 +469,8 @@ class DamageCalculator:
|
||||
- Lightning Spear: 50% / 50%
|
||||
|
||||
Args:
|
||||
attacker_stats: Attacker's Stats
|
||||
attacker_stats: Attacker's Stats (damage and spell_power include equipment)
|
||||
defender_stats: Defender's Stats
|
||||
weapon_damage: Base weapon damage
|
||||
weapon_crit_chance: Crit chance from weapon
|
||||
weapon_crit_multiplier: Crit damage multiplier
|
||||
physical_ratio: Portion of damage that is physical (0.0-1.0)
|
||||
@@ -516,17 +514,15 @@ class DamageCalculator:
|
||||
crit_mult = weapon_crit_multiplier if is_crit else 1.0
|
||||
|
||||
# Step 3: Calculate physical component
|
||||
# Physical uses STR scaling
|
||||
phys_base = (weapon_damage + ability_base_power) * physical_ratio
|
||||
str_bonus = attacker_stats.strength * CombatConstants.STAT_SCALING_FACTOR * physical_ratio
|
||||
phys_damage = (phys_base + str_bonus) * variance * crit_mult
|
||||
# attacker_stats.damage includes: int(STR * 0.75) + damage_bonus (weapon)
|
||||
phys_base = (attacker_stats.damage + ability_base_power) * physical_ratio
|
||||
phys_damage = phys_base * variance * crit_mult
|
||||
phys_final = cls.apply_defense(int(phys_damage), defender_stats.defense)
|
||||
|
||||
# Step 4: Calculate elemental component
|
||||
# Elemental uses INT scaling
|
||||
elem_base = (weapon_damage + ability_base_power) * elemental_ratio
|
||||
int_bonus = attacker_stats.intelligence * CombatConstants.STAT_SCALING_FACTOR * elemental_ratio
|
||||
elem_damage = (elem_base + int_bonus) * variance * crit_mult
|
||||
# attacker_stats.spell_power includes: int(INT * 0.75) + spell_power_bonus (staff/wand)
|
||||
elem_base = (attacker_stats.spell_power + ability_base_power) * elemental_ratio
|
||||
elem_damage = elem_base * variance * crit_mult
|
||||
elem_final = cls.apply_defense(int(elem_damage), defender_stats.resistance)
|
||||
|
||||
# Step 5: Combine results
|
||||
|
||||
Reference in New Issue
Block a user