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:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user