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