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:
@@ -571,17 +571,26 @@ class CombatService:
|
||||
message="Invalid or dead target"
|
||||
)
|
||||
|
||||
# Get attacker's weapon damage (or base damage for enemies)
|
||||
weapon_damage = self._get_weapon_damage(attacker)
|
||||
crit_chance = self._get_crit_chance(attacker)
|
||||
|
||||
# Calculate damage using DamageCalculator
|
||||
damage_result = DamageCalculator.calculate_physical_damage(
|
||||
attacker_stats=attacker.stats,
|
||||
defender_stats=target.stats,
|
||||
weapon_damage=weapon_damage,
|
||||
weapon_crit_chance=crit_chance,
|
||||
)
|
||||
# Check if this is an elemental weapon attack
|
||||
if attacker.elemental_ratio > 0.0 and attacker.elemental_damage_type:
|
||||
# Elemental weapon: split damage between physical and elemental
|
||||
damage_result = DamageCalculator.calculate_elemental_weapon_damage(
|
||||
attacker_stats=attacker.stats,
|
||||
defender_stats=target.stats,
|
||||
weapon_crit_chance=attacker.weapon_crit_chance,
|
||||
weapon_crit_multiplier=attacker.weapon_crit_multiplier,
|
||||
physical_ratio=attacker.physical_ratio,
|
||||
elemental_ratio=attacker.elemental_ratio,
|
||||
elemental_type=attacker.elemental_damage_type,
|
||||
)
|
||||
else:
|
||||
# Normal physical attack
|
||||
damage_result = DamageCalculator.calculate_physical_damage(
|
||||
attacker_stats=attacker.stats,
|
||||
defender_stats=target.stats,
|
||||
weapon_crit_chance=attacker.weapon_crit_chance,
|
||||
weapon_crit_multiplier=attacker.weapon_crit_multiplier,
|
||||
)
|
||||
|
||||
# Add target_id to result for tracking
|
||||
damage_result.target_id = target.combatant_id
|
||||
@@ -970,6 +979,25 @@ class CombatService:
|
||||
abilities = ["basic_attack"] # All characters have basic attack
|
||||
abilities.extend(character.unlocked_skills)
|
||||
|
||||
# Extract weapon properties from equipped weapon
|
||||
weapon = character.equipped.get("weapon")
|
||||
weapon_crit_chance = 0.05
|
||||
weapon_crit_multiplier = 2.0
|
||||
weapon_damage_type = DamageType.PHYSICAL
|
||||
elemental_damage_type = None
|
||||
physical_ratio = 1.0
|
||||
elemental_ratio = 0.0
|
||||
|
||||
if weapon and weapon.is_weapon():
|
||||
weapon_crit_chance = weapon.crit_chance
|
||||
weapon_crit_multiplier = weapon.crit_multiplier
|
||||
weapon_damage_type = weapon.damage_type or DamageType.PHYSICAL
|
||||
|
||||
if weapon.is_elemental_weapon():
|
||||
elemental_damage_type = weapon.elemental_damage_type
|
||||
physical_ratio = weapon.physical_ratio
|
||||
elemental_ratio = weapon.elemental_ratio
|
||||
|
||||
return Combatant(
|
||||
combatant_id=character.character_id,
|
||||
name=character.name,
|
||||
@@ -980,6 +1008,12 @@ class CombatService:
|
||||
max_mp=effective_stats.mana_points,
|
||||
stats=effective_stats,
|
||||
abilities=abilities,
|
||||
weapon_crit_chance=weapon_crit_chance,
|
||||
weapon_crit_multiplier=weapon_crit_multiplier,
|
||||
weapon_damage_type=weapon_damage_type,
|
||||
elemental_damage_type=elemental_damage_type,
|
||||
physical_ratio=physical_ratio,
|
||||
elemental_ratio=elemental_ratio,
|
||||
)
|
||||
|
||||
def _create_combatant_from_enemy(
|
||||
@@ -996,7 +1030,9 @@ class CombatService:
|
||||
if instance_index > 0:
|
||||
name = f"{template.name} #{instance_index + 1}"
|
||||
|
||||
stats = template.base_stats
|
||||
# Copy stats and populate damage_bonus with base_damage
|
||||
stats = template.base_stats.copy()
|
||||
stats.damage_bonus = template.base_damage
|
||||
|
||||
return Combatant(
|
||||
combatant_id=combatant_id,
|
||||
@@ -1008,23 +1044,15 @@ class CombatService:
|
||||
max_mp=stats.mana_points,
|
||||
stats=stats,
|
||||
abilities=template.abilities.copy(),
|
||||
weapon_crit_chance=template.crit_chance,
|
||||
weapon_crit_multiplier=2.0,
|
||||
weapon_damage_type=DamageType.PHYSICAL,
|
||||
)
|
||||
|
||||
def _get_weapon_damage(self, combatant: Combatant) -> int:
|
||||
"""Get weapon damage for a combatant."""
|
||||
# For enemies, use base_damage from template
|
||||
if not combatant.is_player:
|
||||
# Base damage stored in combatant data or default
|
||||
return 8 # Default enemy damage
|
||||
|
||||
# For players, would check equipped weapon
|
||||
# TODO: Check character's equipped weapon
|
||||
return 5 # Default unarmed damage
|
||||
|
||||
def _get_crit_chance(self, combatant: Combatant) -> float:
|
||||
"""Get critical hit chance for a combatant."""
|
||||
# Base 5% + LUK bonus
|
||||
return 0.05 + combatant.stats.crit_bonus
|
||||
# Weapon crit chance + LUK bonus
|
||||
return combatant.weapon_crit_chance + combatant.stats.crit_bonus
|
||||
|
||||
def _get_default_target(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user