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:
2025-11-26 19:54:58 -06:00
parent 4ced1b04df
commit a38906b445
16 changed files with 792 additions and 168 deletions

View File

@@ -1,9 +1,9 @@
# Phase 4: Combat & Progression Systems - Implementation Plan
**Status:** In Progress - Week 2 In Progress
**Status:** In Progress - Week 2 Complete, Week 3 Next
**Timeline:** 4-5 weeks
**Last Updated:** November 26, 2025
**Document Version:** 1.1
**Document Version:** 1.3
---
@@ -35,6 +35,31 @@
**Total Tests:** 108 passing
### Week 2: Inventory & Equipment - COMPLETE
| Task | Description | Status | Tests |
|------|-------------|--------|-------|
| 2.1 | Item Data Models (Affixes) | ✅ Complete | 24 tests |
| 2.2 | Item Data Files (YAML) | ✅ Complete | - |
| 2.2.1 | Item Generator Service | ✅ Complete | 35 tests |
| 2.3 | Inventory Service | ✅ Complete | 24 tests |
| 2.4 | Inventory API Endpoints | ✅ Complete | 25 tests |
| 2.5 | Character Stats Calculation | ✅ Complete | 17 tests |
| 2.6 | Equipment-Combat Integration | ✅ Complete | 140 tests |
**Files Created/Modified:**
- `/api/app/models/items.py` - Item with affix support, spell_power field
- `/api/app/models/affixes.py` - Affix, BaseItemTemplate dataclasses
- `/api/app/models/stats.py` - spell_power_bonus, updated damage formula
- `/api/app/models/combat.py` - Combatant weapon properties
- `/api/app/services/item_generator.py` - Procedural item generation
- `/api/app/services/inventory_service.py` - Equipment management
- `/api/app/services/damage_calculator.py` - Refactored to use stats properties
- `/api/app/services/combat_service.py` - Equipment integration
- `/api/app/api/inventory.py` - REST API endpoints
**Total Tests (Week 2):** 265+ passing
---
## Overview
@@ -973,7 +998,7 @@ app.register_blueprint(combat_bp, url_prefix='/api/v1/combat')
---
### Week 2: Inventory & Equipment System ⏳ IN PROGRESS
### Week 2: Inventory & Equipment System ✅ COMPLETE
#### Task 2.1: Item Data Models ✅ COMPLETE
@@ -1563,81 +1588,172 @@ character.inventory.append(generated_item.to_dict()) # Store full item data
---
#### Task 2.5: Update Character Stats Calculation (4 hours)
#### Task 2.5: Update Character Stats Calculation (4 hours) ✅ COMPLETE
**Objective:** Ensure `get_effective_stats()` includes equipped items
**Objective:** Ensure `get_effective_stats()` includes equipped items' combat bonuses
**File:** `/api/app/models/character.py`
**Files Modified:**
- `/api/app/models/stats.py` - Added `damage_bonus`, `defense_bonus`, `resistance_bonus` fields
- `/api/app/models/character.py` - Updated `get_effective_stats()` to populate bonus fields
**Update Method:**
**Implementation Summary:**
The Stats model now has three equipment bonus fields that are populated by `get_effective_stats()`:
```python
def get_effective_stats(self) -> Stats:
"""
Calculate effective stats including base, equipment, skills, and effects.
# Stats model additions
damage_bonus: int = 0 # From weapons
defense_bonus: int = 0 # From armor
resistance_bonus: int = 0 # From armor
Returns:
Stats instance with all modifiers applied
"""
# Start with base stats
effective = Stats(
strength=self.stats.strength,
defense=self.stats.defense,
speed=self.stats.speed,
intelligence=self.stats.intelligence,
resistance=self.stats.resistance,
vitality=self.stats.vitality,
spirit=self.stats.spirit
)
# Updated computed properties
@property
def damage(self) -> int:
return (self.strength // 2) + self.damage_bonus
# Add bonuses from equipped items
from app.services.item_loader import ItemLoader
item_loader = ItemLoader()
@property
def defense(self) -> int:
return (self.constitution // 2) + self.defense_bonus
for slot, item_id in self.equipped.items():
item = item_loader.get_item(item_id)
if not item:
continue
# Add item stat bonuses
if hasattr(item, 'stat_bonuses'):
for stat_name, bonus in item.stat_bonuses.items():
current_value = getattr(effective, stat_name)
setattr(effective, stat_name, current_value + bonus)
# Armor adds defense/resistance
if item.item_type == ItemType.ARMOR:
effective.defense += item.defense
effective.resistance += item.resistance
# Add bonuses from unlocked skills
for skill_id in self.unlocked_skills:
skill = self.skill_tree.get_skill_node(skill_id)
if skill and skill.stat_bonuses:
for stat_name, bonus in skill.stat_bonuses.items():
current_value = getattr(effective, stat_name)
setattr(effective, stat_name, current_value + bonus)
# Add temporary effects (buffs/debuffs)
for effect in self.active_effects:
if effect.effect_type in [EffectType.BUFF, EffectType.DEBUFF]:
modifier = effect.power * effect.stacks
if effect.effect_type == EffectType.DEBUFF:
modifier *= -1
current_value = getattr(effective, effect.stat_type)
new_value = max(1, current_value + modifier) # Min stat is 1
setattr(effective, effect.stat_type, new_value)
return effective
@property
def resistance(self) -> int:
return (self.wisdom // 2) + self.resistance_bonus
```
**Acceptance Criteria:**
- Equipped weapons add damage
- Equipped armor adds defense/resistance
- Stat bonuses from items apply correctly
- Skills still apply bonuses
- Effects still modify stats
The `get_effective_stats()` method now applies:
1. `stat_bonuses` dict from all equipped items (as before)
2. Weapon `damage` → `damage_bonus`
3. Armor `defense` → `defense_bonus`
4. Armor `resistance` → `resistance_bonus`
**Tests Added:**
- `/api/tests/test_stats.py` - 11 new tests for bonus fields
- `/api/tests/test_character.py` - 6 new tests for equipment combat bonuses
**Acceptance Criteria:** ✅ MET
- [x] Equipped weapons add damage (via `damage_bonus`)
- [x] Equipped armor adds defense/resistance (via `defense_bonus`/`resistance_bonus`)
- [x] Stat bonuses from items apply correctly
- [x] Skills still apply bonuses
- [x] Effects still modify stats
---
#### Task 2.6: Equipment-Combat Integration (4 hours) ✅ COMPLETE
**Objective:** Fully integrate equipment stats into combat damage calculations, replacing hardcoded weapon damage values with effective_stats properties.
**Files Modified:**
- `/api/app/models/stats.py` - Updated damage formula, added spell_power system
- `/api/app/models/items.py` - Added spell_power field for magical weapons
- `/api/app/models/character.py` - Populate spell_power_bonus in get_effective_stats()
- `/api/app/models/combat.py` - Added weapon property fields to Combatant
- `/api/app/services/combat_service.py` - Updated combatant creation and attack execution
- `/api/app/services/damage_calculator.py` - Use stats properties instead of weapon_damage param
**Implementation Summary:**
**1. Updated Damage Formula (Stats Model)**
Changed damage scaling from `STR // 2` to `int(STR * 0.75)` for better progression:
```python
# Old formula
@property
def damage(self) -> int:
return (self.strength // 2) + self.damage_bonus
# New formula (0.75 scaling factor)
@property
def damage(self) -> int:
return int(self.strength * 0.75) + self.damage_bonus
```
**2. Added Spell Power System**
Symmetric system for magical weapons (staves, wands):
```python
# Stats model additions
spell_power_bonus: int = 0 # From magical weapons
@property
def spell_power(self) -> int:
"""Magical damage: int(INT * 0.75) + spell_power_bonus."""
return int(self.intelligence * 0.75) + self.spell_power_bonus
# Item model additions
spell_power: int = 0 # Spell power bonus for magical weapons
def is_magical_weapon(self) -> bool:
"""Check if this is a magical weapon (uses spell_power)."""
return self.is_weapon() and self.spell_power > 0
```
**3. Combatant Weapon Properties**
Added weapon properties to Combatant model for combat-time access:
```python
# Weapon combat properties
weapon_crit_chance: float = 0.05
weapon_crit_multiplier: float = 2.0
weapon_damage_type: Optional[DamageType] = None
# Elemental weapon support
elemental_damage_type: Optional[DamageType] = None
physical_ratio: float = 1.0
elemental_ratio: float = 0.0
```
**4. DamageCalculator Refactored**
Removed `weapon_damage` parameter - now uses `attacker_stats.damage` directly:
```python
# Old signature
def calculate_physical_damage(
attacker_stats: Stats,
defender_stats: Stats,
weapon_damage: int, # Separate parameter
...
)
# New signature
def calculate_physical_damage(
attacker_stats: Stats, # stats.damage includes weapon bonus
defender_stats: Stats,
...
)
# Formula now uses:
base_damage = attacker_stats.damage + ability_base_power # Physical
base_damage = attacker_stats.spell_power + ability_base_power # Magical
```
**5. Combat Service Updates**
- `_create_combatant_from_character()` extracts weapon properties from equipped weapon
- `_create_combatant_from_enemy()` uses `stats.damage_bonus = template.base_damage`
- Removed hardcoded `_get_weapon_damage()` method
- `_execute_attack()` handles elemental weapons with split damage
**Tests Updated:**
- `/api/tests/test_stats.py` - Updated damage formula tests (0.75 scaling)
- `/api/tests/test_character.py` - Updated equipment bonus tests
- `/api/tests/test_damage_calculator.py` - Removed weapon_damage parameter from calls
- `/api/tests/test_combat_service.py` - Added `equipped` attribute to mock fixture
**Test Results:** 140 tests passing for all modified components
**Acceptance Criteria:** ✅ MET
- [x] Damage uses `effective_stats.damage` (includes weapon bonus)
- [x] Spell power uses `effective_stats.spell_power` (includes staff/wand bonus)
- [x] 0.75 scaling factor for both physical and magical damage
- [x] Weapon crit chance/multiplier flows through to combat
- [x] Elemental weapons support split physical/elemental damage
- [x] Enemy combatants use template base_damage correctly
- [x] All existing tests pass with updated formulas
---