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

@@ -708,6 +708,149 @@ success = service.soft_delete_message(
- **Consumable:** One-time use (potions, scrolls)
- **Quest Item:** Story-related, non-tradeable
---
## Procedural Item Generation (Affix System)
The game uses a Diablo-style procedural item generation system where weapons and armor
are created by combining base templates with random affixes.
### Core Models
#### Affix
Represents a prefix or suffix that modifies an item's stats and name.
| Field | Type | Description |
|-------|------|-------------|
| `affix_id` | str | Unique identifier |
| `name` | str | Display name ("Flaming", "of Strength") |
| `affix_type` | AffixType | PREFIX or SUFFIX |
| `tier` | AffixTier | MINOR, MAJOR, or LEGENDARY |
| `description` | str | Affix description |
| `stat_bonuses` | Dict[str, int] | Stat modifications |
| `damage_bonus` | int | Flat damage increase |
| `defense_bonus` | int | Flat defense increase |
| `resistance_bonus` | int | Flat resistance increase |
| `damage_type` | DamageType | For elemental affixes |
| `elemental_ratio` | float | Portion of damage converted to element |
| `crit_chance_bonus` | float | Critical hit chance modifier |
| `crit_multiplier_bonus` | float | Critical damage modifier |
| `allowed_item_types` | List[str] | Item types this affix can apply to |
| `required_rarity` | str | Minimum rarity required (for legendary affixes) |
**Methods:**
- `applies_elemental_damage() -> bool` - Check if affix adds elemental damage
- `is_legendary_only() -> bool` - Check if requires legendary rarity
- `can_apply_to(item_type, rarity) -> bool` - Check if affix can be applied
#### BaseItemTemplate
Foundation template for procedural item generation.
| Field | Type | Description |
|-------|------|-------------|
| `template_id` | str | Unique identifier |
| `name` | str | Base item name ("Dagger") |
| `item_type` | str | "weapon" or "armor" |
| `description` | str | Template description |
| `base_damage` | int | Starting damage value |
| `base_defense` | int | Starting defense value |
| `base_resistance` | int | Starting resistance value |
| `base_value` | int | Base gold value |
| `damage_type` | str | Physical, fire, etc. |
| `crit_chance` | float | Base critical chance |
| `crit_multiplier` | float | Base critical multiplier |
| `required_level` | int | Minimum level to use |
| `min_rarity` | str | Minimum rarity this generates as |
| `drop_weight` | int | Relative drop probability |
**Methods:**
- `can_generate_at_rarity(rarity) -> bool` - Check if template supports rarity
- `can_drop_for_level(level) -> bool` - Check level requirement
### Item Model Updates for Generated Items
The `Item` dataclass includes fields for tracking generated items:
| Field | Type | Description |
|-------|------|-------------|
| `applied_affixes` | List[str] | IDs of affixes on this item |
| `base_template_id` | str | ID of base template used |
| `generated_name` | str | Full name with affixes (e.g., "Flaming Dagger of Strength") |
| `is_generated` | bool | True if procedurally generated |
**Methods:**
- `get_display_name() -> str` - Returns generated_name if available, otherwise base name
### Generation Enumerations
#### ItemRarity
Item quality tiers affecting affix count and value:
| Value | Affix Count | Value Multiplier |
|-------|-------------|------------------|
| `COMMON` | 0 | 1.0× |
| `UNCOMMON` | 0 | 1.5× |
| `RARE` | 1 | 2.5× |
| `EPIC` | 2 | 5.0× |
| `LEGENDARY` | 3 | 10.0× |
#### AffixType
| Value | Description |
|-------|-------------|
| `PREFIX` | Appears before item name ("Flaming Dagger") |
| `SUFFIX` | Appears after item name ("Dagger of Strength") |
#### AffixTier
Affix power level, determines eligibility by item rarity:
| Value | Description | Available For |
|-------|-------------|---------------|
| `MINOR` | Basic affixes | RARE+ |
| `MAJOR` | Stronger affixes | RARE+ (higher weight at EPIC+) |
| `LEGENDARY` | Most powerful affixes | LEGENDARY only |
### Item Generation Service
**Location:** `/app/services/item_generator.py`
**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
)
# Generate random loot drop with luck influence
item = generator.generate_loot_drop(
character_level=10,
luck_stat=12
)
```
**Related Loaders:**
- `AffixLoader` (`/app/services/affix_loader.py`) - Loads affix definitions from YAML
- `BaseItemLoader` (`/app/services/base_item_loader.py`) - Loads base templates from YAML
**Data Files:**
- `/app/data/affixes/prefixes.yaml` - Prefix definitions
- `/app/data/affixes/suffixes.yaml` - Suffix definitions
- `/app/data/base_items/weapons.yaml` - Weapon templates
- `/app/data/base_items/armor.yaml` - Armor templates
---
### Ability
Abilities represent actions that can be taken in combat (attacks, spells, skills, item uses).

View File

@@ -402,6 +402,111 @@ effects_applied:
---
## Procedural Item Generation
### Overview
Weapons and armor are procedurally generated using a Diablo-style affix system.
Items are created by combining:
1. **Base Template** - Defines item type, base stats, level requirement
2. **Affixes** - Prefixes and suffixes that add stats and modify the name
### Generation Process
1. Select base template (filtered by level, rarity)
2. Determine affix count based on rarity (0-3)
3. Roll affix tier based on rarity weights
4. Select random affixes avoiding duplicates
5. Combine stats and generate name
### Rarity System
| Rarity | Affixes | Value Multiplier | Color |
|--------|---------|------------------|-------|
| COMMON | 0 | 1.0× | Gray |
| UNCOMMON | 0 | 1.5× | Green |
| RARE | 1 | 2.5× | Blue |
| EPIC | 2 | 5.0× | Purple |
| LEGENDARY | 3 | 10.0× | Orange |
### Affix Distribution
| Rarity | Affix Count | Distribution |
|--------|-------------|--------------|
| RARE | 1 | 50% prefix OR 50% suffix |
| EPIC | 2 | 1 prefix AND 1 suffix |
| LEGENDARY | 3 | Mix (2+1 or 1+2) |
### Affix Tiers
Higher rarity items have better chances at higher tier affixes:
| Rarity | MINOR | MAJOR | LEGENDARY |
|--------|-------|-------|-----------|
| RARE | 80% | 20% | 0% |
| EPIC | 30% | 70% | 0% |
| LEGENDARY | 10% | 40% | 50% |
### Name Generation Examples
- **COMMON:** "Dagger"
- **RARE (prefix):** "Flaming Dagger"
- **RARE (suffix):** "Dagger of Strength"
- **EPIC:** "Flaming Dagger of Strength"
- **LEGENDARY:** "Blazing Glacial Dagger of the Titan"
### Luck Influence
Player's LUK stat affects rarity rolls for loot drops:
**Base chances at LUK 8:**
- COMMON: 50%
- UNCOMMON: 30%
- RARE: 15%
- EPIC: 4%
- LEGENDARY: 1%
**Luck Bonus:**
Each point of LUK above 8 adds +0.5% to higher rarity chances.
**Examples:**
- LUK 8 (baseline): 1% legendary chance
- LUK 12: ~3% legendary chance
- LUK 16: ~5% legendary chance
### Service Usage
```python
from app.services.item_generator import get_item_generator
from app.models.enums import ItemRarity
generator = get_item_generator()
# Generate item of specific rarity
sword = generator.generate_item(
item_type="weapon",
rarity=ItemRarity.EPIC,
character_level=5
)
# Generate random loot with luck bonus
loot = generator.generate_loot_drop(
character_level=10,
luck_stat=15
)
```
### Data Files
| File | Description |
|------|-------------|
| `/app/data/base_items/weapons.yaml` | 13 weapon templates |
| `/app/data/base_items/armor.yaml` | 12 armor templates |
| `/app/data/affixes/prefixes.yaml` | 18 prefix affixes |
| `/app/data/affixes/suffixes.yaml` | 11 suffix affixes |
---
## Quest System (Future)
### Quest Types