227 lines
8.4 KiB
Markdown
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
|