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:
@@ -12,7 +12,7 @@ import random
|
||||
from app.models.stats import Stats
|
||||
from app.models.effects import Effect
|
||||
from app.models.abilities import Ability
|
||||
from app.models.enums import CombatStatus, EffectType
|
||||
from app.models.enums import CombatStatus, EffectType, DamageType
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -36,6 +36,12 @@ class Combatant:
|
||||
abilities: Available abilities for this combatant
|
||||
cooldowns: Map of ability_id to turns remaining
|
||||
initiative: Turn order value (rolled at combat start)
|
||||
weapon_crit_chance: Critical hit chance from equipped weapon
|
||||
weapon_crit_multiplier: Critical hit damage multiplier
|
||||
weapon_damage_type: Primary damage type of weapon
|
||||
elemental_damage_type: Secondary damage type for elemental weapons
|
||||
physical_ratio: Portion of damage that is physical (0.0-1.0)
|
||||
elemental_ratio: Portion of damage that is elemental (0.0-1.0)
|
||||
"""
|
||||
|
||||
combatant_id: str
|
||||
@@ -51,6 +57,16 @@ class Combatant:
|
||||
cooldowns: Dict[str, int] = field(default_factory=dict)
|
||||
initiative: int = 0
|
||||
|
||||
# Weapon properties (for combat calculations)
|
||||
weapon_crit_chance: float = 0.05
|
||||
weapon_crit_multiplier: float = 2.0
|
||||
weapon_damage_type: Optional[DamageType] = None
|
||||
|
||||
# Elemental weapon properties (for split damage)
|
||||
elemental_damage_type: Optional[DamageType] = None
|
||||
physical_ratio: float = 1.0
|
||||
elemental_ratio: float = 0.0
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
"""Check if combatant is still alive."""
|
||||
return self.current_hp > 0
|
||||
@@ -228,6 +244,12 @@ class Combatant:
|
||||
"abilities": self.abilities,
|
||||
"cooldowns": self.cooldowns,
|
||||
"initiative": self.initiative,
|
||||
"weapon_crit_chance": self.weapon_crit_chance,
|
||||
"weapon_crit_multiplier": self.weapon_crit_multiplier,
|
||||
"weapon_damage_type": self.weapon_damage_type.value if self.weapon_damage_type else None,
|
||||
"elemental_damage_type": self.elemental_damage_type.value if self.elemental_damage_type else None,
|
||||
"physical_ratio": self.physical_ratio,
|
||||
"elemental_ratio": self.elemental_ratio,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -236,6 +258,15 @@ class Combatant:
|
||||
stats = Stats.from_dict(data["stats"])
|
||||
active_effects = [Effect.from_dict(e) for e in data.get("active_effects", [])]
|
||||
|
||||
# Parse damage types
|
||||
weapon_damage_type = None
|
||||
if data.get("weapon_damage_type"):
|
||||
weapon_damage_type = DamageType(data["weapon_damage_type"])
|
||||
|
||||
elemental_damage_type = None
|
||||
if data.get("elemental_damage_type"):
|
||||
elemental_damage_type = DamageType(data["elemental_damage_type"])
|
||||
|
||||
return cls(
|
||||
combatant_id=data["combatant_id"],
|
||||
name=data["name"],
|
||||
@@ -249,6 +280,12 @@ class Combatant:
|
||||
abilities=data.get("abilities", []),
|
||||
cooldowns=data.get("cooldowns", {}),
|
||||
initiative=data.get("initiative", 0),
|
||||
weapon_crit_chance=data.get("weapon_crit_chance", 0.05),
|
||||
weapon_crit_multiplier=data.get("weapon_crit_multiplier", 2.0),
|
||||
weapon_damage_type=weapon_damage_type,
|
||||
elemental_damage_type=elemental_damage_type,
|
||||
physical_ratio=data.get("physical_ratio", 1.0),
|
||||
elemental_ratio=data.get("elemental_ratio", 0.0),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user