EU-Utility/loadout_redesign.md

8.4 KiB

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

@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

# 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

# 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
@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