EU-Utility/loadout_redesign.md

227 lines
8.4 KiB
Markdown

# Loadout Manager Redesign - Cost Tracking Focus
## Current Problems
1. **Three overlapping armor systems:**
- Legacy: `armor_decay_pec` + `protection_stab/cut/etc`
- EquippedArmor: `equipped_armor` with ArmorPiece/ArmorSet
- New system: `current_armor_*` fields
2. **Serialization nightmare:** ProtectionProfile, ArmorPiece, complex nested objects
3. **Session integration broken:** Cost values don't flow from Loadout → Session
## Core Insight
**For cost tracking, we only need three numbers:**
- `weapon_cost_per_shot` (PED)
- `armor_cost_per_hit` (PED)
- `healing_cost_per_heal` (PED)
Everything else is display metadata.
## Proposed Simple Structure
```python
@dataclass
class LoadoutConfig:
"""Simple loadout focused on cost tracking."""
name: str
# === COST DATA (Required for tracking) ===
# Weapon
weapon_cost_per_shot: Decimal # Pre-calculated total (decay + ammo)
# Armor
armor_cost_per_hit: Decimal # Pre-calculated decay per hit
# Healing
healing_cost_per_heal: Decimal # Pre-calculated decay per heal
# === DISPLAY METADATA (Optional, for UI only) ===
weapon_name: str = "Unknown"
weapon_damage: Decimal = Decimal("0")
weapon_decay_pec: Decimal = Decimal("0") # Raw value for reference
weapon_ammo_pec: Decimal = Decimal("0") # Raw value for reference
armor_name: str = "None"
armor_decay_pec: Decimal = Decimal("0") # Raw value for reference
healing_name: str = "None"
healing_decay_pec: Decimal = Decimal("0") # Raw value for reference
# === UI STATE (Not serialized) ===
# These are populated when loading from API/database
# but not saved to JSON - they're derived data
weapon_api_id: Optional[int] = None
armor_api_id: Optional[int] = None
healing_api_id: Optional[int] = None
def to_dict(self) -> dict:
"""Simple serialization - just the basics."""
return {
'name': self.name,
'weapon_cost_per_shot': str(self.weapon_cost_per_shot),
'armor_cost_per_hit': str(self.armor_cost_per_hit),
'healing_cost_per_heal': str(self.healing_cost_per_heal),
'weapon_name': self.weapon_name,
'weapon_damage': str(self.weapon_damage),
'weapon_decay_pec': str(self.weapon_decay_pec),
'weapon_ammo_pec': str(self.weapon_ammo_pec),
'armor_name': self.armor_name,
'armor_decay_pec': str(self.armor_decay_pec),
'healing_name': self.healing_name,
'healing_decay_pec': str(self.healing_decay_pec),
}
@classmethod
def from_dict(cls, data: dict) -> "LoadoutConfig":
"""Simple deserialization with safe defaults."""
def get_decimal(key, default="0"):
try:
return Decimal(str(data.get(key, default)))
except:
return Decimal(default)
return cls(
name=data.get('name', 'Unnamed'),
weapon_cost_per_shot=get_decimal('weapon_cost_per_shot'),
armor_cost_per_hit=get_decimal('armor_cost_per_hit'),
healing_cost_per_heal=get_decimal('healing_cost_per_heal'),
weapon_name=data.get('weapon_name', 'Unknown'),
weapon_damage=get_decimal('weapon_damage'),
weapon_decay_pec=get_decimal('weapon_decay_pec'),
weapon_ammo_pec=get_decimal('weapon_ammo_pec'),
armor_name=data.get('armor_name', 'None'),
armor_decay_pec=get_decimal('armor_decay_pec'),
healing_name=data.get('healing_name', 'None'),
healing_decay_pec=get_decimal('healing_decay_pec'),
)
```
## UI Simplification
### Loadout Manager Dialog
**Single purpose:** Configure gear and calculate costs
**Layout:**
```
┌─────────────────────────────────────────┐
│ Loadout: [Name ] │
├─────────────────────────────────────────┤
│ ⚔️ WEAPON │
│ [Select Weapon...] ArMatrix BP-25 │
│ Damage: 85 Decay: 0.688 PEC │
│ Ammo: 848 Cost/Shot: 0.091 PED │
├─────────────────────────────────────────┤
│ 🛡️ ARMOR │
│ [Select Armor...] Ghost │
│ Decay/Hit: 0.015 PEC = 0.00015 PED │
├─────────────────────────────────────────┤
│ 💚 HEALING │
│ [Select Healing...] Regen Chip 4 │
│ Heal: 45 HP Cost/Heal: 0.028 PED │
├─────────────────────────────────────────┤
│ 💰 SESSION COST SUMMARY │
│ Cost/Shot: 0.091 PED │
│ Cost/Hit: 0.00015 PED │
│ Cost/Heal: 0.028 PED │
├─────────────────────────────────────────┤
│ [Save] [Cancel] │
└─────────────────────────────────────────┘
```
### Key Changes
1. **No more armor piece management** - Just select armor set, get decay value
2. **No more plate management** - Include plate decay in armor_cost_per_hit
3. **Pre-calculated costs** - All conversions happen on save
4. **Simple JSON** - Only cost values + display names
## Session Integration
### Flow: Start Session
```python
# In LoadoutSelectionDialog
loadout_info = {
'id': 0, # 0 = file-based
'name': 'ArMatrix Ghost Hunt',
'source': 'file',
'costs': {
'cost_per_shot': Decimal('0.091'),
'cost_per_hit': Decimal('0.00015'),
'cost_per_heal': Decimal('0.028'),
},
'display': {
'weapon_name': 'ArMatrix BP-25 (L)',
'armor_name': 'Ghost',
'healing_name': 'Regeneration Chip 4 (L)',
}
}
# In MainWindow._on_loadout_selected_for_session
self._session_costs = loadout_info['costs']
self._session_display = loadout_info['display']
# In MainWindow.start_session
self.hud.start_session(
weapon=self._session_display['weapon_name'],
armor=self._session_display['armor_name'],
healing=self._session_display['healing_name'],
cost_per_shot=self._session_costs['cost_per_shot'],
cost_per_hit=self._session_costs['cost_per_hit'],
cost_per_heal=self._session_costs['cost_per_heal'],
)
```
### Cost Tracking Logic
```python
# In MainWindow (log event handlers)
def on_shot_fired():
"""Called when weapon is fired."""
if self._session_costs:
self.hud.update_weapon_cost(self._session_costs['cost_per_shot'])
def on_damage_taken(amount):
"""Called when player takes damage (armor hit)."""
if self._session_costs:
self.hud.update_armor_cost(self._session_costs['cost_per_hit'])
def on_heal_used():
"""Called when healing tool is used."""
if self._session_costs:
self.hud.update_healing_cost(self._session_costs['cost_per_heal'])
```
## Migration Strategy
1. **Keep existing files** - Don't break old loadouts
2. **Add version field** - `version: 2` for new format
3. **On load:** If old format, extract decay values and convert
4. **On save:** Always write new simple format
```python
@classmethod
def from_dict(cls, data: dict) -> "LoadoutConfig":
version = data.get('version', 1)
if version == 1:
# Legacy format - extract cost data
return cls._from_legacy(data)
else:
# New simple format
return cls._from_v2(data)
```
## Files to Modify
1. `ui/loadout_manager.py` - Complete rewrite of LoadoutConfig
2. `ui/loadout_selection_dialog.py` - Simplify to extract costs only
3. `ui/main_window.py` - Clean cost tracking integration
4. `ui/hud_overlay.py` - Already accepts costs, just verify display
## Benefits
1. **Simple serialization** - No more Decimal/ProtectionProfile issues
2. **Clear data flow** - Costs calculated once, used everywhere
3. **Easy debugging** - Look at JSON, see exactly what costs are
4. **Fast loading** - No complex object reconstruction
5. **Reliable** - Less code = fewer bugs