feat(api): implement inventory service with equipment system

Add InventoryService for managing character inventory, equipment, and
consumable usage. Key features:

- Add/remove items with inventory capacity checks
- Equipment slot validation (weapon, off_hand, helmet, chest, gloves,
  boots, accessory_1, accessory_2)
- Level and class requirement validation for equipment
- Consumable usage with instant and duration-based effects
- Combat-specific consumable method returning effects for combat system
- Bulk operations (add_items, get_items_by_type, get_equippable_items)

Design decision: Uses full Item object storage (not IDs) to support
procedurally generated items with unique identifiers.

Files added:
- /api/app/services/inventory_service.py (560 lines)
- /api/tests/test_inventory_service.py (51 tests passing)

Task 2.3 of Phase 4 Combat Implementation complete.
This commit is contained in:
2025-11-26 18:38:39 -06:00
parent 185be7fee0
commit 76f67c4a22
5 changed files with 2207 additions and 79 deletions

View File

@@ -1,6 +1,6 @@
# Phase 4: Combat & Progression Systems - Implementation Plan
**Status:** In Progress - Week 1 Complete
**Status:** In Progress - Week 2 In Progress
**Timeline:** 4-5 weeks
**Last Updated:** November 26, 2025
**Document Version:** 1.1
@@ -973,108 +973,250 @@ app.register_blueprint(combat_bp, url_prefix='/api/v1/combat')
---
### Week 2: Inventory & Equipment System ⏳ NEXT
### Week 2: Inventory & Equipment System ⏳ IN PROGRESS
#### Task 2.1: Verify Item Data Models (2 hours)
#### Task 2.1: Item Data Models ✅ COMPLETE
**Objective:** Review item system implementation
**Objective:** Implement Diablo-style item generation with affixes
**Files to Review:**
- `/api/app/models/items.py` - Item, ItemType, ItemRarity
- `/api/app/models/enums.py` - ItemType enum
**Files Implemented:**
- `/api/app/models/items.py` - Item dataclass with affix support
- `/api/app/models/affixes.py` - Affix, BaseItemTemplate dataclasses
- `/api/app/models/enums.py` - ItemRarity, AffixType, AffixTier enums
**Verification Checklist:**
- [ ] Item dataclass complete with all fields
- [ ] ItemType enum: WEAPON, ARMOR, CONSUMABLE, QUEST_ITEM
- [ ] Item has `to_dict()` and `from_dict()` methods
- [ ] Weapon-specific fields: damage, crit_chance, crit_multiplier
- [ ] Armor-specific fields: defense, resistance
- [ ] Consumable-specific fields: effects
**Item Model - New Fields for Generated Items:**
**Acceptance Criteria:**
- Item model can represent all item types
- Serialization works correctly
```python
@dataclass
class Item:
# ... existing fields ...
# Affix tracking (for generated items)
applied_affixes: List[str] = field(default_factory=list)
base_template_id: Optional[str] = None
generated_name: Optional[str] = None
is_generated: bool = False
def get_display_name(self) -> str:
"""Return generated name if available, otherwise base name."""
return self.generated_name if self.generated_name else self.name
```
**Affix Model:**
```python
@dataclass
class Affix:
affix_id: str
name: str # Display name ("Flaming", "of Strength")
affix_type: AffixType # PREFIX or SUFFIX
tier: AffixTier # MINOR, MAJOR, LEGENDARY
stat_bonuses: Dict[str, int] # {"strength": 3, "dexterity": 2}
damage_bonus: int = 0
defense_bonus: int = 0
damage_type: Optional[DamageType] = None # For elemental prefixes
elemental_ratio: float = 0.0
allowed_item_types: List[str] = field(default_factory=list)
```
**BaseItemTemplate Model:**
```python
@dataclass
class BaseItemTemplate:
template_id: str
name: str # "Dagger", "Longsword"
item_type: str # "weapon" or "armor"
base_damage: int = 0
base_defense: int = 0
base_value: int = 0
required_level: int = 1
min_rarity: str = "common" # Minimum rarity this can generate as
drop_weight: int = 100 # Relative drop chance
```
**Acceptance Criteria:** ✅ MET
- Item model supports both static and generated items
- Affix system with PREFIX/SUFFIX types
- Three affix tiers (MINOR, MAJOR, LEGENDARY)
- BaseItemTemplate for procedural generation foundation
- All models have to_dict()/from_dict() serialization
---
#### Task 2.2: Create Starting Items YAML (4 hours)
#### Task 2.2: Item Data Files ✅ COMPLETE
**Objective:** Define 20-30 basic items in YAML
**Objective:** Create YAML data files for item generation system
**Directory Structure:**
```
/api/app/data/items/
├── weapons/
── swords.yaml
├── bows.yaml
│ └── staves.yaml
├── armor/
── helmets.yaml
│ ├── chest.yaml
── boots.yaml
└── consumables/
└── potions.yaml
/api/app/data/
├── items/ # Static items (consumables, quest items)
── consumables/
└── potions.yaml
├── base_items/ # Base templates for generation
│ ├── weapons.yaml # 13 weapon templates
── armor.yaml # 12 armor templates
└── affixes/ # Prefix/suffix definitions
── prefixes.yaml # 18 prefixes
└── suffixes.yaml # 11 suffixes
```
**Example: `/api/app/data/items/weapons/swords.yaml`**
**Example Base Weapon Template (`/api/app/data/base_items/weapons.yaml`):**
```yaml
- item_id: "iron_sword"
name: "Iron Sword"
description: "A sturdy iron blade. Reliable and affordable."
dagger:
template_id: "dagger"
name: "Dagger"
item_type: "weapon"
rarity: "common"
value: 50
damage: 10
crit_chance: 0.05
crit_multiplier: 2.0
required_level: 1
is_tradeable: true
- item_id: "steel_sword"
name: "Steel Sword"
description: "Forged from high-quality steel. Sharper and more durable."
item_type: "weapon"
rarity: "uncommon"
value: 150
damage: 18
base_damage: 6
damage_type: "physical"
crit_chance: 0.08
crit_multiplier: 2.0
required_level: 3
is_tradeable: true
- item_id: "enchanted_blade"
name: "Enchanted Blade"
description: "A sword infused with magical energy."
item_type: "weapon"
rarity: "rare"
value: 500
damage: 30
crit_chance: 0.12
crit_multiplier: 2.5
required_level: 7
is_tradeable: true
base_value: 15
required_level: 1
drop_weight: 100
```
**Create Items:**
- **Weapons** (10 items): Swords, bows, staves, daggers (common → legendary)
- **Armor** (10 items): Helmets, chest armor, boots (light/medium/heavy)
- **Consumables** (10 items): Health potions (small/medium/large), mana potions, antidotes, scrolls
**Example Prefix Affix (`/api/app/data/affixes/prefixes.yaml`):**
**Acceptance Criteria:**
- At least 20 items defined
- Mix of item types and rarities
- Balanced stats for level requirements
- All YAML files valid and loadable
```yaml
flaming:
affix_id: "flaming"
name: "Flaming"
affix_type: "prefix"
tier: "minor"
damage_type: "fire"
elemental_ratio: 0.25
damage_bonus: 3
allowed_item_types: ["weapon"]
```
**Example Suffix Affix (`/api/app/data/affixes/suffixes.yaml`):**
```yaml
of_strength:
affix_id: "of_strength"
name: "of Strength"
affix_type: "suffix"
tier: "minor"
stat_bonuses:
strength: 3
```
**Items Created:**
- **Base Templates:** 25 total (13 weapons, 12 armor across cloth/leather/chain/plate)
- **Prefixes:** 18 total (elemental, material, quality, defensive, legendary)
- **Suffixes:** 11 total (stat bonuses, animal totems, defensive, legendary)
- **Static Consumables:** Health/mana potions (small/medium/large)
**Acceptance Criteria:** ✅ MET
- Base templates cover all weapon/armor categories
- Affixes balanced across tiers
- YAML files valid and loadable
---
#### Task 2.3: Implement Inventory Service (1 day / 8 hours)
#### Task 2.2.1: Item Generator Service ✅ COMPLETE
**Objective:** Implement procedural item generation with Diablo-style naming
**Files Implemented:**
- `/api/app/services/item_generator.py` - Main generation service (535 lines)
- `/api/app/services/affix_loader.py` - Loads affixes from YAML (316 lines)
- `/api/app/services/base_item_loader.py` - Loads base templates from YAML (274 lines)
**ItemGenerator Usage:**
```python
from app.services.item_generator import get_item_generator
from app.models.enums import ItemRarity
generator = get_item_generator()
# Generate specific item
item = generator.generate_item(
item_type="weapon",
rarity=ItemRarity.EPIC,
character_level=5
)
# Result: "Flaming Longsword of Strength" (1 prefix + 1 suffix)
# Generate random loot drop with luck influence
item = generator.generate_loot_drop(
character_level=10,
luck_stat=12 # Higher luck = better rarity chance
)
```
**Affix Distribution by Rarity:**
| Rarity | Affix Count | Distribution |
|--------|-------------|--------------|
| COMMON | 0 | Plain item |
| UNCOMMON | 0 | Plain item |
| RARE | 1 | 50% prefix OR 50% suffix |
| EPIC | 2 | 1 prefix AND 1 suffix |
| LEGENDARY | 3 | Mix (2+1 or 1+2) |
**Name Generation Examples:**
- COMMON: "Dagger"
- RARE: "Flaming Dagger" or "Dagger of Strength"
- EPIC: "Flaming Dagger of Strength"
- LEGENDARY: "Blazing Glacial Dagger of the Titan"
**Tier Weights by Rarity:**
| Rarity | MINOR | MAJOR | LEGENDARY |
|--------|-------|-------|-----------|
| RARE | 80% | 20% | 0% |
| EPIC | 30% | 70% | 0% |
| LEGENDARY | 10% | 40% | 50% |
**Rarity Rolling (with Luck):**
Base chances at luck 8:
- COMMON: 50%
- UNCOMMON: 30%
- RARE: 15%
- EPIC: 4%
- LEGENDARY: 1%
Luck bonus: `(luck - 8) * 0.005` per threshold
**Tests:** `/api/tests/test_item_generator.py` (528 lines, comprehensive coverage)
**Acceptance Criteria:** ✅ MET
- Procedural generation works for all rarities
- Affix selection respects tier weights
- Generated names follow Diablo naming convention
- Luck stat influences rarity rolls
- Stats properly combined from template + affixes
---
#### Task 2.3: Implement Inventory Service (1 day / 8 hours) ✅ COMPLETE
**Objective:** Service layer for inventory management
**File:** `/api/app/services/inventory_service.py`
**Actual Implementation:**
The InventoryService was implemented as an orchestration layer on top of the existing Character model inventory methods. Key design decisions:
1. **Full Object Storage (Not IDs)**: The Character model already stores `List[Item]` for inventory and `Dict[str, Item]` for equipped items. This approach works better for generated items which have unique IDs.
2. **Validation Layer**: Added comprehensive validation for:
- Equipment slot validation (weapon, off_hand, helmet, chest, gloves, boots, accessory_1, accessory_2)
- Level and class requirements
- Item type to slot mapping
3. **Consumable Effects**: Supports instant healing (HOT effects) and duration-based buffs for combat integration.
4. **Tests**: 51 unit tests covering all functionality.
**Implementation:**
```python
@@ -1329,12 +1471,30 @@ class ItemLoader:
return self.items
```
**Acceptance Criteria:**
- Inventory service can add/remove items
- Equip/unequip works with validation
- Consumables can be used (healing, mana restore)
- Item loader caches all items from YAML
- Character's equipped items tracked
**Note on Generated Items:**
The inventory service must handle both static items (loaded by ID) and generated items
(stored as full objects). Generated items have unique IDs (`gen_<uuid>`) and cannot be
looked up from YAML - they must be stored/retrieved as complete Item objects.
```python
# For static items (consumables, quest items)
item = item_loader.get_item("health_potion_small")
# For generated items - store full object
generated_item = generator.generate_loot_drop(level, luck)
character.inventory.append(generated_item.to_dict()) # Store full item data
```
**Acceptance Criteria:** ✅ MET
- [x] Inventory service can add/remove items - `add_item()`, `remove_item()`, `drop_item()`
- [x] Equip/unequip works with validation - `equip_item()`, `unequip_item()` with slot/level/type checks
- [x] Consumables can be used (healing, mana restore) - `use_consumable()`, `use_consumable_in_combat()`
- [x] Character's equipped items tracked - via `get_equipped_items()`, `get_equipped_item()`
- [x] **Generated items stored as full objects (not just IDs)** - Character model uses `List[Item]`
- [x] Bulk operations - `add_items()`, `get_items_by_type()`, `get_equippable_items()`
**Tests:** `/api/tests/test_inventory_service.py` - 51 tests
---
@@ -1579,6 +1739,40 @@ def get_effective_stats(self) -> Stats:
---
### Future Work: Combat Loot Integration
**Status:** Planned for future phase
The ItemGenerator is ready for integration with combat loot drops. Future implementation will:
**1. Update Enemy Loot Tables** - Add procedural generation options:
```yaml
# Example enhanced enemy loot entry
loot_table:
- type: "static"
item_id: "health_potion_small"
drop_chance: 0.5
- type: "generated"
item_type: "weapon"
rarity_range: ["rare", "epic"]
drop_chance: 0.1
```
**2. Integrate with CombatService._calculate_rewards()** - Use ItemGenerator for loot rolls
**3. Boss Guaranteed Drops** - Higher-tier enemies guarantee better rarity
**4. Luck Stat Integration** - Player luck affects all loot rolls
**Implementation Notes:**
- Current enemy loot tables use `item_id` references (static items only)
- ItemGenerator provides `generate_loot_drop(character_level, luck_stat)` method
- Generated items must be stored as full objects (not IDs) in character inventory
- Consider adding `LootService` wrapper for consistent loot generation across all sources
---
### Week 3: Combat UI
#### Task 3.1: Create Combat Template (1 day / 8 hours)