refactor: simplified cost-focused Loadout Manager
- New LoadoutManagerSimple with clean cost-focused design - LoadoutConfig now stores only: cost_per_shot/hit/heal + display names - Legacy format support for backward compatibility - Simplified LoadoutSelectionDialog with clear cost preview - Updated MainWindow to use new simplified structure - Removed 3 overlapping armor systems, replaced with single decay value - JSON serialization is now simple and reliable Key principle: Only store what's needed for cost tracking.
This commit is contained in:
parent
83084252cc
commit
cdc9f5b825
|
|
@ -0,0 +1,667 @@
|
||||||
|
"""
|
||||||
|
Lemontropia Suite - Loadout Manager UI v4.0
|
||||||
|
Simplified cost-focused loadout system.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from decimal import Decimal
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
|
||||||
|
QLineEdit, QLabel, QPushButton, QGroupBox,
|
||||||
|
QMessageBox, QListWidget, QListWidgetItem,
|
||||||
|
QSplitter, QWidget, QFrame, QGridLayout,
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
|
|
||||||
|
from core.nexus_full_api import get_nexus_api, NexusWeapon
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Simple Cost-Focused Loadout Config
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LoadoutConfig:
|
||||||
|
"""Simple loadout configuration focused on cost tracking.
|
||||||
|
|
||||||
|
Core principle: Only store what's needed for cost calculations.
|
||||||
|
Everything else is display metadata.
|
||||||
|
"""
|
||||||
|
# Identity
|
||||||
|
name: str = "Unnamed"
|
||||||
|
version: int = 2 # Version 2 = simplified format
|
||||||
|
|
||||||
|
# === COST DATA (Required for tracking) ===
|
||||||
|
# All values in PED (not PEC)
|
||||||
|
weapon_cost_per_shot: Decimal = Decimal("0")
|
||||||
|
armor_cost_per_hit: Decimal = Decimal("0")
|
||||||
|
healing_cost_per_heal: Decimal = Decimal("0")
|
||||||
|
|
||||||
|
# === DISPLAY METADATA (For UI only) ===
|
||||||
|
weapon_name: str = "None"
|
||||||
|
weapon_damage: Decimal = Decimal("0")
|
||||||
|
weapon_decay_pec: Decimal = Decimal("0") # Raw for reference
|
||||||
|
weapon_ammo_pec: Decimal = Decimal("0") # Raw for reference
|
||||||
|
|
||||||
|
armor_name: str = "None"
|
||||||
|
armor_decay_pec: Decimal = Decimal("0") # Raw for reference
|
||||||
|
|
||||||
|
healing_name: str = "None"
|
||||||
|
healing_decay_pec: Decimal = Decimal("0") # Raw for reference
|
||||||
|
|
||||||
|
# === API REFERENCES (For re-loading from API) ===
|
||||||
|
weapon_api_id: Optional[int] = None
|
||||||
|
armor_api_id: Optional[int] = None
|
||||||
|
healing_api_id: Optional[int] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Serialize to simple dictionary."""
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'version': self.version,
|
||||||
|
'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),
|
||||||
|
'weapon_api_id': self.weapon_api_id,
|
||||||
|
'armor_api_id': self.armor_api_id,
|
||||||
|
'healing_api_id': self.healing_api_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> "LoadoutConfig":
|
||||||
|
"""Deserialize from dictionary with legacy support."""
|
||||||
|
version = data.get('version', 1)
|
||||||
|
|
||||||
|
if version == 1:
|
||||||
|
return cls._from_legacy(data)
|
||||||
|
else:
|
||||||
|
return cls._from_v2(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_v2(cls, data: dict) -> "LoadoutConfig":
|
||||||
|
"""Parse version 2 (current) format."""
|
||||||
|
def get_decimal(key: str, default: str = "0") -> Decimal:
|
||||||
|
try:
|
||||||
|
return Decimal(str(data.get(key, default)))
|
||||||
|
except Exception:
|
||||||
|
return Decimal(default)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
name=data.get('name', 'Unnamed'),
|
||||||
|
version=2,
|
||||||
|
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', 'None'),
|
||||||
|
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'),
|
||||||
|
weapon_api_id=data.get('weapon_api_id'),
|
||||||
|
armor_api_id=data.get('armor_api_id'),
|
||||||
|
healing_api_id=data.get('healing_api_id'),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_legacy(cls, data: dict) -> "LoadoutConfig":
|
||||||
|
"""Convert legacy format to new simple format."""
|
||||||
|
def get_decimal(key: str, default: str = "0") -> Decimal:
|
||||||
|
try:
|
||||||
|
return Decimal(str(data.get(key, default)))
|
||||||
|
except Exception:
|
||||||
|
return Decimal(default)
|
||||||
|
|
||||||
|
# Calculate costs from legacy fields
|
||||||
|
weapon_decay = get_decimal('weapon_decay_pec')
|
||||||
|
weapon_ammo = get_decimal('weapon_ammo_pec')
|
||||||
|
weapon_cost_per_shot = (weapon_decay / Decimal("100")) + (weapon_ammo * Decimal("0.0001"))
|
||||||
|
|
||||||
|
armor_decay = get_decimal('armor_decay_pec')
|
||||||
|
armor_cost_per_hit = armor_decay / Decimal("100")
|
||||||
|
|
||||||
|
heal_decay = get_decimal('heal_cost_pec')
|
||||||
|
healing_cost_per_heal = heal_decay / Decimal("100")
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
name=data.get('name', 'Unnamed'),
|
||||||
|
version=2,
|
||||||
|
weapon_cost_per_shot=weapon_cost_per_shot,
|
||||||
|
armor_cost_per_hit=armor_cost_per_hit,
|
||||||
|
healing_cost_per_heal=healing_cost_per_heal,
|
||||||
|
weapon_name=data.get('weapon_name', data.get('weapon', 'None')),
|
||||||
|
weapon_damage=get_decimal('weapon_damage'),
|
||||||
|
weapon_decay_pec=weapon_decay,
|
||||||
|
weapon_ammo_pec=weapon_ammo,
|
||||||
|
armor_name=data.get('armor_set_name', data.get('armor_name', 'None')),
|
||||||
|
armor_decay_pec=armor_decay,
|
||||||
|
healing_name=data.get('heal_name', 'None'),
|
||||||
|
healing_decay_pec=heal_decay,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_summary(self) -> Dict[str, Any]:
|
||||||
|
"""Get cost summary for display."""
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'weapon': self.weapon_name,
|
||||||
|
'armor': self.armor_name,
|
||||||
|
'healing': self.healing_name,
|
||||||
|
'cost_per_shot': self.weapon_cost_per_shot,
|
||||||
|
'cost_per_hit': self.armor_cost_per_hit,
|
||||||
|
'cost_per_heal': self.healing_cost_per_heal,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Simple Loadout Manager Dialog
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class LoadoutManagerDialog(QDialog):
|
||||||
|
"""Simplified loadout manager focused on cost configuration."""
|
||||||
|
|
||||||
|
loadout_saved = pyqtSignal(LoadoutConfig)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Loadout Manager")
|
||||||
|
self.setMinimumSize(600, 500)
|
||||||
|
|
||||||
|
# State
|
||||||
|
self.config_dir = Path.home() / ".lemontropia" / "loadouts"
|
||||||
|
self.config_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.current_config: Optional[LoadoutConfig] = None
|
||||||
|
|
||||||
|
# Cached API data
|
||||||
|
self._cached_weapons: Optional[list] = None
|
||||||
|
self._cached_armors: Optional[list] = None
|
||||||
|
self._cached_healing: Optional[list] = None
|
||||||
|
|
||||||
|
self._setup_ui()
|
||||||
|
self._load_saved_loadouts()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
"""Setup simplified UI."""
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# === Top: Loadout Name ===
|
||||||
|
name_layout = QHBoxLayout()
|
||||||
|
name_layout.addWidget(QLabel("Loadout Name:"))
|
||||||
|
self.name_edit = QLineEdit()
|
||||||
|
self.name_edit.setPlaceholderText("e.g., ArMatrix Ghost Hunt")
|
||||||
|
name_layout.addWidget(self.name_edit)
|
||||||
|
layout.addLayout(name_layout)
|
||||||
|
|
||||||
|
# === Main Content Splitter ===
|
||||||
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
|
|
||||||
|
# Left: Saved Loadouts
|
||||||
|
left_widget = QWidget()
|
||||||
|
left_layout = QVBoxLayout(left_widget)
|
||||||
|
left_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
left_layout.addWidget(QLabel("Saved Loadouts:"))
|
||||||
|
self.saved_list = QListWidget()
|
||||||
|
self.saved_list.itemClicked.connect(self._on_loadout_selected)
|
||||||
|
self.saved_list.itemDoubleClicked.connect(self._on_loadout_double_clicked)
|
||||||
|
left_layout.addWidget(self.saved_list)
|
||||||
|
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
self.new_btn = QPushButton("New")
|
||||||
|
self.new_btn.clicked.connect(self._new_loadout)
|
||||||
|
self.delete_btn = QPushButton("Delete")
|
||||||
|
self.delete_btn.clicked.connect(self._delete_loadout)
|
||||||
|
btn_layout.addWidget(self.new_btn)
|
||||||
|
btn_layout.addWidget(self.delete_btn)
|
||||||
|
left_layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
splitter.addWidget(left_widget)
|
||||||
|
|
||||||
|
# Right: Configuration
|
||||||
|
right_widget = QWidget()
|
||||||
|
right_layout = QVBoxLayout(right_widget)
|
||||||
|
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# -- Weapon Section --
|
||||||
|
weapon_group = QGroupBox("⚔️ Weapon")
|
||||||
|
weapon_layout = QFormLayout(weapon_group)
|
||||||
|
|
||||||
|
self.weapon_btn = QPushButton("Select Weapon...")
|
||||||
|
self.weapon_btn.clicked.connect(self._select_weapon)
|
||||||
|
weapon_layout.addRow("Weapon:", self.weapon_btn)
|
||||||
|
|
||||||
|
self.weapon_info = QLabel("None selected")
|
||||||
|
self.weapon_info.setStyleSheet("color: #888;")
|
||||||
|
weapon_layout.addRow(self.weapon_info)
|
||||||
|
|
||||||
|
self.weapon_decay_label = QLabel("0 PEC")
|
||||||
|
weapon_layout.addRow("Decay:", self.weapon_decay_label)
|
||||||
|
|
||||||
|
self.weapon_ammo_label = QLabel("0")
|
||||||
|
weapon_layout.addRow("Ammo:", self.weapon_ammo_label)
|
||||||
|
|
||||||
|
self.weapon_cost_label = QLabel("0.0000 PED")
|
||||||
|
self.weapon_cost_label.setStyleSheet("font-weight: bold; color: #7FFF7F;")
|
||||||
|
weapon_layout.addRow("Cost/Shot:", self.weapon_cost_label)
|
||||||
|
|
||||||
|
right_layout.addWidget(weapon_group)
|
||||||
|
|
||||||
|
# -- Armor Section --
|
||||||
|
armor_group = QGroupBox("🛡️ Armor")
|
||||||
|
armor_layout = QFormLayout(armor_group)
|
||||||
|
|
||||||
|
self.armor_btn = QPushButton("Select Armor...")
|
||||||
|
self.armor_btn.clicked.connect(self._select_armor)
|
||||||
|
armor_layout.addRow("Armor:", self.armor_btn)
|
||||||
|
|
||||||
|
self.armor_info = QLabel("None selected")
|
||||||
|
self.armor_info.setStyleSheet("color: #888;")
|
||||||
|
armor_layout.addRow(self.armor_info)
|
||||||
|
|
||||||
|
self.armor_cost_label = QLabel("0.0000 PED")
|
||||||
|
self.armor_cost_label.setStyleSheet("font-weight: bold; color: #7FFF7F;")
|
||||||
|
armor_layout.addRow("Cost/Hit:", self.armor_cost_label)
|
||||||
|
|
||||||
|
right_layout.addWidget(armor_group)
|
||||||
|
|
||||||
|
# -- Healing Section --
|
||||||
|
healing_group = QGroupBox("💚 Healing")
|
||||||
|
healing_layout = QFormLayout(healing_group)
|
||||||
|
|
||||||
|
self.healing_btn = QPushButton("Select Healing...")
|
||||||
|
self.healing_btn.clicked.connect(self._select_healing)
|
||||||
|
healing_layout.addRow("Healing:", self.healing_btn)
|
||||||
|
|
||||||
|
self.healing_info = QLabel("None selected")
|
||||||
|
self.healing_info.setStyleSheet("color: #888;")
|
||||||
|
healing_layout.addRow(self.healing_info)
|
||||||
|
|
||||||
|
self.healing_cost_label = QLabel("0.0000 PED")
|
||||||
|
self.healing_cost_label.setStyleSheet("font-weight: bold; color: #7FFF7F;")
|
||||||
|
healing_layout.addRow("Cost/Heal:", self.healing_cost_label)
|
||||||
|
|
||||||
|
right_layout.addWidget(healing_group)
|
||||||
|
|
||||||
|
# -- Summary Section --
|
||||||
|
summary_group = QGroupBox("💰 Session Cost Summary")
|
||||||
|
summary_layout = QGridLayout(summary_group)
|
||||||
|
|
||||||
|
summary_layout.addWidget(QLabel("Cost per Shot:"), 0, 0)
|
||||||
|
self.summary_shot = QLabel("0.0000 PED")
|
||||||
|
summary_layout.addWidget(self.summary_shot, 0, 1)
|
||||||
|
|
||||||
|
summary_layout.addWidget(QLabel("Cost per Hit:"), 1, 0)
|
||||||
|
self.summary_hit = QLabel("0.0000 PED")
|
||||||
|
summary_layout.addWidget(self.summary_hit, 1, 1)
|
||||||
|
|
||||||
|
summary_layout.addWidget(QLabel("Cost per Heal:"), 2, 0)
|
||||||
|
self.summary_heal = QLabel("0.0000 PED")
|
||||||
|
summary_layout.addWidget(self.summary_heal, 2, 1)
|
||||||
|
|
||||||
|
summary_layout.setColumnStretch(1, 1)
|
||||||
|
right_layout.addWidget(summary_group)
|
||||||
|
|
||||||
|
right_layout.addStretch()
|
||||||
|
|
||||||
|
splitter.addWidget(right_widget)
|
||||||
|
splitter.setSizes([200, 400])
|
||||||
|
|
||||||
|
layout.addWidget(splitter)
|
||||||
|
|
||||||
|
# === Bottom Buttons ===
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
self.save_btn = QPushButton("💾 Save Loadout")
|
||||||
|
self.save_btn.clicked.connect(self._save_loadout)
|
||||||
|
self.save_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #2E7D32;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #388E3C;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
button_layout.addWidget(self.save_btn)
|
||||||
|
|
||||||
|
self.cancel_btn = QPushButton("Cancel")
|
||||||
|
self.cancel_btn.clicked.connect(self.reject)
|
||||||
|
button_layout.addWidget(self.cancel_btn)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
def _select_weapon(self):
|
||||||
|
"""Open weapon selector dialog."""
|
||||||
|
from ui.weapon_selector import WeaponSelectorDialog
|
||||||
|
|
||||||
|
dialog = WeaponSelectorDialog(self)
|
||||||
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
weapon = dialog.get_selected_weapon()
|
||||||
|
if weapon:
|
||||||
|
self._set_weapon(weapon)
|
||||||
|
|
||||||
|
def _set_weapon(self, weapon: NexusWeapon):
|
||||||
|
"""Set weapon and calculate cost."""
|
||||||
|
# Calculate cost per shot
|
||||||
|
decay_pec = Decimal(str(weapon.decay))
|
||||||
|
ammo = Decimal(str(weapon.ammo))
|
||||||
|
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.weapon_btn.setText(weapon.name[:30])
|
||||||
|
self.weapon_info.setText(f"Damage: {weapon.damage} | Range: {weapon.range}")
|
||||||
|
self.weapon_decay_label.setText(f"{decay_pec} PEC")
|
||||||
|
self.weapon_ammo_label.setText(f"{ammo}")
|
||||||
|
self.weapon_cost_label.setText(f"{cost_per_shot:.4f} PED")
|
||||||
|
|
||||||
|
# Store for saving
|
||||||
|
self._pending_weapon = {
|
||||||
|
'name': weapon.name,
|
||||||
|
'api_id': weapon.id,
|
||||||
|
'damage': weapon.damage,
|
||||||
|
'decay_pec': decay_pec,
|
||||||
|
'ammo_pec': ammo,
|
||||||
|
'cost_per_shot': cost_per_shot,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._update_summary()
|
||||||
|
|
||||||
|
def _select_armor(self):
|
||||||
|
"""Open simplified armor selector."""
|
||||||
|
from ui.armor_selection_dialog import ArmorSelectionDialog
|
||||||
|
|
||||||
|
dialog = ArmorSelectionDialog(self)
|
||||||
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
result = dialog.get_selected_armor()
|
||||||
|
if result:
|
||||||
|
self._set_armor(result)
|
||||||
|
|
||||||
|
def _set_armor(self, armor_data: dict):
|
||||||
|
"""Set armor and calculate cost."""
|
||||||
|
# armor_data has: name, decay_pec, protection_summary
|
||||||
|
name = armor_data.get('name', 'Unknown')
|
||||||
|
decay_pec = Decimal(str(armor_data.get('decay_pec', 0)))
|
||||||
|
cost_per_hit = decay_pec / Decimal("100")
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.armor_btn.setText(name[:30])
|
||||||
|
prot_summary = armor_data.get('protection_summary', '')
|
||||||
|
self.armor_info.setText(prot_summary[:50] if prot_summary else "No protection data")
|
||||||
|
self.armor_cost_label.setText(f"{cost_per_hit:.4f} PED")
|
||||||
|
|
||||||
|
# Store for saving
|
||||||
|
self._pending_armor = {
|
||||||
|
'name': name,
|
||||||
|
'api_id': armor_data.get('api_id'),
|
||||||
|
'decay_pec': decay_pec,
|
||||||
|
'cost_per_hit': cost_per_hit,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._update_summary()
|
||||||
|
|
||||||
|
def _select_healing(self):
|
||||||
|
"""Open healing selector."""
|
||||||
|
from ui.healing_selector import HealingSelectorDialog
|
||||||
|
|
||||||
|
dialog = HealingSelectorDialog(self)
|
||||||
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
healing = dialog.get_selected_healing()
|
||||||
|
if healing:
|
||||||
|
self._set_healing(healing)
|
||||||
|
|
||||||
|
def _set_healing(self, healing_data: dict):
|
||||||
|
"""Set healing and calculate cost."""
|
||||||
|
name = healing_data.get('name', 'Unknown')
|
||||||
|
decay_pec = Decimal(str(healing_data.get('decay_pec', 0)))
|
||||||
|
heal_amount = Decimal(str(healing_data.get('heal_amount', 0)))
|
||||||
|
cost_per_heal = decay_pec / Decimal("100")
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.healing_btn.setText(name[:30])
|
||||||
|
self.healing_info.setText(f"Heal: {heal_amount} HP")
|
||||||
|
self.healing_cost_label.setText(f"{cost_per_heal:.4f} PED")
|
||||||
|
|
||||||
|
# Store for saving
|
||||||
|
self._pending_healing = {
|
||||||
|
'name': name,
|
||||||
|
'api_id': healing_data.get('api_id'),
|
||||||
|
'decay_pec': decay_pec,
|
||||||
|
'heal_amount': heal_amount,
|
||||||
|
'cost_per_heal': cost_per_heal,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._update_summary()
|
||||||
|
|
||||||
|
def _update_summary(self):
|
||||||
|
"""Update cost summary display."""
|
||||||
|
shot = getattr(self, '_pending_weapon', {}).get('cost_per_shot', Decimal("0"))
|
||||||
|
hit = getattr(self, '_pending_armor', {}).get('cost_per_hit', Decimal("0"))
|
||||||
|
heal = getattr(self, '_pending_healing', {}).get('cost_per_heal', Decimal("0"))
|
||||||
|
|
||||||
|
self.summary_shot.setText(f"{shot:.4f} PED")
|
||||||
|
self.summary_hit.setText(f"{hit:.4f} PED")
|
||||||
|
self.summary_heal.setText(f"{heal:.4f} PED")
|
||||||
|
|
||||||
|
def _save_loadout(self):
|
||||||
|
"""Save current configuration."""
|
||||||
|
name = self.name_edit.text().strip()
|
||||||
|
if not name:
|
||||||
|
QMessageBox.warning(self, "Missing Name", "Please enter a loadout name")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build config from pending data
|
||||||
|
weapon = getattr(self, '_pending_weapon', {})
|
||||||
|
armor = getattr(self, '_pending_armor', {})
|
||||||
|
healing = getattr(self, '_pending_healing', {})
|
||||||
|
|
||||||
|
config = LoadoutConfig(
|
||||||
|
name=name,
|
||||||
|
weapon_cost_per_shot=weapon.get('cost_per_shot', Decimal("0")),
|
||||||
|
armor_cost_per_hit=armor.get('cost_per_hit', Decimal("0")),
|
||||||
|
healing_cost_per_heal=healing.get('cost_per_heal', Decimal("0")),
|
||||||
|
weapon_name=weapon.get('name', 'None'),
|
||||||
|
weapon_damage=weapon.get('damage', Decimal("0")),
|
||||||
|
weapon_decay_pec=weapon.get('decay_pec', Decimal("0")),
|
||||||
|
weapon_ammo_pec=weapon.get('ammo_pec', Decimal("0")),
|
||||||
|
armor_name=armor.get('name', 'None'),
|
||||||
|
armor_decay_pec=armor.get('decay_pec', Decimal("0")),
|
||||||
|
healing_name=healing.get('name', 'None'),
|
||||||
|
healing_decay_pec=healing.get('decay_pec', Decimal("0")),
|
||||||
|
weapon_api_id=weapon.get('api_id'),
|
||||||
|
armor_api_id=armor.get('api_id'),
|
||||||
|
healing_api_id=healing.get('api_id'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
safe_name = "".join(c for c in name if c.isalnum() or c in "._- ").strip()
|
||||||
|
if not safe_name:
|
||||||
|
safe_name = "unnamed"
|
||||||
|
|
||||||
|
filepath = self.config_dir / f"{safe_name}.json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, 'w') as f:
|
||||||
|
json.dump(config.to_dict(), f, indent=2)
|
||||||
|
|
||||||
|
self.current_config = config
|
||||||
|
self.loadout_saved.emit(config)
|
||||||
|
self._load_saved_loadouts()
|
||||||
|
|
||||||
|
QMessageBox.information(self, "Saved", f"Loadout '{name}' saved!")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to save: {e}")
|
||||||
|
|
||||||
|
def _load_saved_loadouts(self):
|
||||||
|
"""Load list of saved loadouts."""
|
||||||
|
self.saved_list.clear()
|
||||||
|
|
||||||
|
try:
|
||||||
|
for filepath in sorted(self.config_dir.glob("*.json")):
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
config = LoadoutConfig.from_dict(data)
|
||||||
|
|
||||||
|
item = QListWidgetItem(f"📋 {config.name}")
|
||||||
|
item.setData(Qt.ItemDataRole.UserRole, str(filepath))
|
||||||
|
|
||||||
|
# Tooltip with costs
|
||||||
|
tooltip = (
|
||||||
|
f"Weapon: {config.weapon_name}\n"
|
||||||
|
f"Armor: {config.armor_name}\n"
|
||||||
|
f"Cost/Shot: {config.weapon_cost_per_shot:.4f} PED\n"
|
||||||
|
f"Cost/Hit: {config.armor_cost_per_hit:.4f} PED\n"
|
||||||
|
f"Cost/Heal: {config.healing_cost_per_heal:.4f} PED"
|
||||||
|
)
|
||||||
|
item.setToolTip(tooltip)
|
||||||
|
self.saved_list.addItem(item)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load {filepath}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to list loadouts: {e}")
|
||||||
|
|
||||||
|
def _on_loadout_selected(self, item: QListWidgetItem):
|
||||||
|
"""Load selected loadout into UI."""
|
||||||
|
filepath = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
if not filepath:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
config = LoadoutConfig.from_dict(data)
|
||||||
|
|
||||||
|
self._load_config_into_ui(config)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to load: {e}")
|
||||||
|
|
||||||
|
def _on_loadout_double_clicked(self, item: QListWidgetItem):
|
||||||
|
"""Double-click to select and close."""
|
||||||
|
self._on_loadout_selected(item)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def _load_config_into_ui(self, config: LoadoutConfig):
|
||||||
|
"""Load config values into UI fields."""
|
||||||
|
self.name_edit.setText(config.name)
|
||||||
|
|
||||||
|
# Weapon
|
||||||
|
if config.weapon_name != "None":
|
||||||
|
self.weapon_btn.setText(config.weapon_name[:30])
|
||||||
|
self.weapon_info.setText(f"Damage: {config.weapon_damage}")
|
||||||
|
self.weapon_decay_label.setText(f"{config.weapon_decay_pec} PEC")
|
||||||
|
self.weapon_ammo_label.setText(f"{config.weapon_ammo_pec}")
|
||||||
|
self.weapon_cost_label.setText(f"{config.weapon_cost_per_shot:.4f} PED")
|
||||||
|
self._pending_weapon = {
|
||||||
|
'name': config.weapon_name,
|
||||||
|
'api_id': config.weapon_api_id,
|
||||||
|
'damage': config.weapon_damage,
|
||||||
|
'decay_pec': config.weapon_decay_pec,
|
||||||
|
'ammo_pec': config.weapon_ammo_pec,
|
||||||
|
'cost_per_shot': config.weapon_cost_per_shot,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Armor
|
||||||
|
if config.armor_name != "None":
|
||||||
|
self.armor_btn.setText(config.armor_name[:30])
|
||||||
|
self.armor_info.setText("Loaded from save")
|
||||||
|
self.armor_cost_label.setText(f"{config.armor_cost_per_hit:.4f} PED")
|
||||||
|
self._pending_armor = {
|
||||||
|
'name': config.armor_name,
|
||||||
|
'api_id': config.armor_api_id,
|
||||||
|
'decay_pec': config.armor_decay_pec,
|
||||||
|
'cost_per_hit': config.armor_cost_per_hit,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Healing
|
||||||
|
if config.healing_name != "None":
|
||||||
|
self.healing_btn.setText(config.healing_name[:30])
|
||||||
|
self.healing_cost_label.setText(f"{config.healing_cost_per_heal:.4f} PED")
|
||||||
|
self._pending_healing = {
|
||||||
|
'name': config.healing_name,
|
||||||
|
'api_id': config.healing_api_id,
|
||||||
|
'decay_pec': config.healing_decay_pec,
|
||||||
|
'cost_per_heal': config.healing_cost_per_heal,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._update_summary()
|
||||||
|
self.current_config = config
|
||||||
|
|
||||||
|
def _new_loadout(self):
|
||||||
|
"""Clear all fields for new loadout."""
|
||||||
|
self.name_edit.clear()
|
||||||
|
self.weapon_btn.setText("Select Weapon...")
|
||||||
|
self.weapon_info.setText("None selected")
|
||||||
|
self.weapon_decay_label.setText("0 PEC")
|
||||||
|
self.weapon_ammo_label.setText("0")
|
||||||
|
self.weapon_cost_label.setText("0.0000 PED")
|
||||||
|
|
||||||
|
self.armor_btn.setText("Select Armor...")
|
||||||
|
self.armor_info.setText("None selected")
|
||||||
|
self.armor_cost_label.setText("0.0000 PED")
|
||||||
|
|
||||||
|
self.healing_btn.setText("Select Healing...")
|
||||||
|
self.healing_info.setText("None selected")
|
||||||
|
self.healing_cost_label.setText("0.0000 PED")
|
||||||
|
|
||||||
|
self._pending_weapon = None
|
||||||
|
self._pending_armor = None
|
||||||
|
self._pending_healing = None
|
||||||
|
self._update_summary()
|
||||||
|
self.current_config = None
|
||||||
|
|
||||||
|
def _delete_loadout(self):
|
||||||
|
"""Delete selected loadout."""
|
||||||
|
item = self.saved_list.currentItem()
|
||||||
|
if not item:
|
||||||
|
QMessageBox.information(self, "No Selection", "Please select a loadout to delete")
|
||||||
|
return
|
||||||
|
|
||||||
|
filepath = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
name = item.text().replace("📋 ", "")
|
||||||
|
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self, "Confirm Delete",
|
||||||
|
f"Delete '{name}'?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
|
try:
|
||||||
|
os.remove(filepath)
|
||||||
|
self._load_saved_loadouts()
|
||||||
|
self._new_loadout()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to delete: {e}")
|
||||||
|
|
||||||
|
def get_config(self) -> Optional[LoadoutConfig]:
|
||||||
|
"""Get current configuration."""
|
||||||
|
return self.current_config
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Backward Compatibility
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Keep old names for imports
|
||||||
|
LoadoutManager = LoadoutManagerDialog
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
"""
|
||||||
|
Lemontropia Suite - Loadout Selection Dialog v2.0
|
||||||
|
Simplified cost-focused selection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from decimal import Decimal
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
|
QPushButton, QListWidget, QListWidgetItem,
|
||||||
|
QMessageBox, QWidget, QGridLayout
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
|
|
||||||
|
from ui.loadout_manager_simple import LoadoutConfig
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class LoadoutSelectionDialog(QDialog):
|
||||||
|
"""Simplified dialog for selecting a loadout to start a session.
|
||||||
|
|
||||||
|
Emits cost data that can be used directly by MainWindow for tracking.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Signal emits: {
|
||||||
|
# 'id': 0, # 0 = file-based
|
||||||
|
# 'name': 'Loadout Name',
|
||||||
|
# 'source': 'file',
|
||||||
|
# 'costs': {
|
||||||
|
# 'cost_per_shot': Decimal,
|
||||||
|
# 'cost_per_hit': Decimal,
|
||||||
|
# 'cost_per_heal': Decimal,
|
||||||
|
# },
|
||||||
|
# 'display': {
|
||||||
|
# 'weapon_name': str,
|
||||||
|
# 'armor_name': str,
|
||||||
|
# 'healing_name': str,
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
loadout_selected = pyqtSignal(dict)
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Select Loadout for Session")
|
||||||
|
self.setMinimumSize(500, 400)
|
||||||
|
|
||||||
|
self.config_dir = Path.home() / ".lemontropia" / "loadouts"
|
||||||
|
self.selected_loadout = None
|
||||||
|
|
||||||
|
self._setup_ui()
|
||||||
|
self._load_loadouts()
|
||||||
|
|
||||||
|
def _setup_ui(self):
|
||||||
|
"""Setup simple UI."""
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header = QLabel("Select a loadout to track costs during your session:")
|
||||||
|
header.setStyleSheet("font-size: 12px; color: #888;")
|
||||||
|
layout.addWidget(header)
|
||||||
|
|
||||||
|
# Loadout list
|
||||||
|
self.loadout_list = QListWidget()
|
||||||
|
self.loadout_list.itemClicked.connect(self._on_select)
|
||||||
|
self.loadout_list.itemDoubleClicked.connect(self._on_double_click)
|
||||||
|
layout.addWidget(self.loadout_list)
|
||||||
|
|
||||||
|
# Preview panel
|
||||||
|
self.preview = QWidget()
|
||||||
|
preview_layout = QGridLayout(self.preview)
|
||||||
|
|
||||||
|
# Weapon
|
||||||
|
preview_layout.addWidget(QLabel("⚔️ Weapon:"), 0, 0)
|
||||||
|
self.preview_weapon = QLabel("-")
|
||||||
|
preview_layout.addWidget(self.preview_weapon, 0, 1)
|
||||||
|
|
||||||
|
preview_layout.addWidget(QLabel(" Cost/Shot:"), 1, 0)
|
||||||
|
self.preview_cost_shot = QLabel("-")
|
||||||
|
self.preview_cost_shot.setStyleSheet("color: #7FFF7F;")
|
||||||
|
preview_layout.addWidget(self.preview_cost_shot, 1, 1)
|
||||||
|
|
||||||
|
# Armor
|
||||||
|
preview_layout.addWidget(QLabel("🛡️ Armor:"), 2, 0)
|
||||||
|
self.preview_armor = QLabel("-")
|
||||||
|
preview_layout.addWidget(self.preview_armor, 2, 1)
|
||||||
|
|
||||||
|
preview_layout.addWidget(QLabel(" Cost/Hit:"), 3, 0)
|
||||||
|
self.preview_cost_hit = QLabel("-")
|
||||||
|
self.preview_cost_hit.setStyleSheet("color: #7FFF7F;")
|
||||||
|
preview_layout.addWidget(self.preview_cost_hit, 3, 1)
|
||||||
|
|
||||||
|
# Healing
|
||||||
|
preview_layout.addWidget(QLabel("💚 Healing:"), 4, 0)
|
||||||
|
self.preview_healing = QLabel("-")
|
||||||
|
preview_layout.addWidget(self.preview_healing, 4, 1)
|
||||||
|
|
||||||
|
preview_layout.addWidget(QLabel(" Cost/Heal:"), 5, 0)
|
||||||
|
self.preview_cost_heal = QLabel("-")
|
||||||
|
self.preview_cost_heal.setStyleSheet("color: #7FFF7F;")
|
||||||
|
preview_layout.addWidget(self.preview_cost_heal, 5, 1)
|
||||||
|
|
||||||
|
preview_layout.setColumnStretch(1, 1)
|
||||||
|
layout.addWidget(self.preview)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
self.skip_btn = QPushButton("Skip (No Cost Tracking)")
|
||||||
|
self.skip_btn.clicked.connect(self._on_skip)
|
||||||
|
self.skip_btn.setStyleSheet("color: #888;")
|
||||||
|
button_layout.addWidget(self.skip_btn)
|
||||||
|
|
||||||
|
self.ok_btn = QPushButton("Start Session ▶")
|
||||||
|
self.ok_btn.clicked.connect(self._on_accept)
|
||||||
|
self.ok_btn.setEnabled(False)
|
||||||
|
self.ok_btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #2E7D32;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:disabled {
|
||||||
|
background-color: #333;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
button_layout.addWidget(self.ok_btn)
|
||||||
|
|
||||||
|
self.cancel_btn = QPushButton("Cancel")
|
||||||
|
self.cancel_btn.clicked.connect(self.reject)
|
||||||
|
button_layout.addWidget(self.cancel_btn)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
def _load_loadouts(self):
|
||||||
|
"""Load all saved loadouts."""
|
||||||
|
self.loadout_list.clear()
|
||||||
|
self.loadouts = []
|
||||||
|
|
||||||
|
if not self.config_dir.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
for filepath in sorted(self.config_dir.glob("*.json")):
|
||||||
|
try:
|
||||||
|
with open(filepath, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
config = LoadoutConfig.from_dict(data)
|
||||||
|
|
||||||
|
# Build display text
|
||||||
|
has_costs = (
|
||||||
|
config.weapon_cost_per_shot > 0 or
|
||||||
|
config.armor_cost_per_hit > 0 or
|
||||||
|
config.healing_cost_per_heal > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
display = f"📋 {config.name}"
|
||||||
|
if has_costs:
|
||||||
|
display += f" (💰 {config.weapon_cost_per_shot:.3f}/shot)"
|
||||||
|
|
||||||
|
item = QListWidgetItem(display)
|
||||||
|
item.setData(Qt.ItemDataRole.UserRole, config)
|
||||||
|
|
||||||
|
# Tooltip
|
||||||
|
tooltip = (
|
||||||
|
f"Weapon: {config.weapon_name}\n"
|
||||||
|
f"Armor: {config.armor_name}\n"
|
||||||
|
f"Healing: {config.healing_name}\n"
|
||||||
|
f"\n"
|
||||||
|
f"Cost/Shot: {config.weapon_cost_per_shot:.4f} PED\n"
|
||||||
|
f"Cost/Hit: {config.armor_cost_per_hit:.4f} PED\n"
|
||||||
|
f"Cost/Heal: {config.healing_cost_per_heal:.4f} PED"
|
||||||
|
)
|
||||||
|
item.setToolTip(tooltip)
|
||||||
|
|
||||||
|
self.loadout_list.addItem(item)
|
||||||
|
self.loadouts.append(config)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load {filepath}: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to list loadouts: {e}")
|
||||||
|
|
||||||
|
if not self.loadouts:
|
||||||
|
item = QListWidgetItem("(No saved loadouts)")
|
||||||
|
item.setFlags(Qt.ItemFlag.NoItemFlags)
|
||||||
|
self.loadout_list.addItem(item)
|
||||||
|
|
||||||
|
def _on_select(self, item: QListWidgetItem):
|
||||||
|
"""Update preview when loadout selected."""
|
||||||
|
config = item.data(Qt.ItemDataRole.UserRole)
|
||||||
|
if not isinstance(config, LoadoutConfig):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.selected_loadout = config
|
||||||
|
|
||||||
|
# Update preview
|
||||||
|
self.preview_weapon.setText(config.weapon_name if config.weapon_name != "None" else "Not set")
|
||||||
|
self.preview_armor.setText(config.armor_name if config.armor_name != "None" else "Not set")
|
||||||
|
self.preview_healing.setText(config.healing_name if config.healing_name != "None" else "Not set")
|
||||||
|
|
||||||
|
self.preview_cost_shot.setText(f"{config.weapon_cost_per_shot:.4f} PED")
|
||||||
|
self.preview_cost_hit.setText(f"{config.armor_cost_per_hit:.4f} PED")
|
||||||
|
self.preview_cost_heal.setText(f"{config.healing_cost_per_heal:.4f} PED")
|
||||||
|
|
||||||
|
self.ok_btn.setEnabled(True)
|
||||||
|
|
||||||
|
def _on_double_click(self, item: QListWidgetItem):
|
||||||
|
"""Double-click to select immediately."""
|
||||||
|
self._on_select(item)
|
||||||
|
self._on_accept()
|
||||||
|
|
||||||
|
def _on_skip(self):
|
||||||
|
"""Start session without cost tracking."""
|
||||||
|
self.loadout_selected.emit({
|
||||||
|
'id': 0,
|
||||||
|
'name': 'No Loadout',
|
||||||
|
'source': 'none',
|
||||||
|
'costs': {
|
||||||
|
'cost_per_shot': Decimal('0'),
|
||||||
|
'cost_per_hit': Decimal('0'),
|
||||||
|
'cost_per_heal': Decimal('0'),
|
||||||
|
},
|
||||||
|
'display': {
|
||||||
|
'weapon_name': 'None',
|
||||||
|
'armor_name': 'None',
|
||||||
|
'healing_name': 'None',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def _on_accept(self):
|
||||||
|
"""Emit selected loadout and close."""
|
||||||
|
if not self.selected_loadout:
|
||||||
|
QMessageBox.warning(self, "No Selection", "Please select a loadout")
|
||||||
|
return
|
||||||
|
|
||||||
|
config = self.selected_loadout
|
||||||
|
|
||||||
|
self.loadout_selected.emit({
|
||||||
|
'id': 0, # File-based
|
||||||
|
'name': config.name,
|
||||||
|
'source': 'file',
|
||||||
|
'costs': {
|
||||||
|
'cost_per_shot': config.weapon_cost_per_shot,
|
||||||
|
'cost_per_hit': config.armor_cost_per_hit,
|
||||||
|
'cost_per_heal': config.healing_cost_per_heal,
|
||||||
|
},
|
||||||
|
'display': {
|
||||||
|
'weapon_name': config.weapon_name,
|
||||||
|
'armor_name': config.armor_name,
|
||||||
|
'healing_name': config.healing_name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.accept()
|
||||||
|
|
@ -936,23 +936,23 @@ class MainWindow(QMainWindow):
|
||||||
# Show HUD and start session tracking
|
# Show HUD and start session tracking
|
||||||
self.hud.show()
|
self.hud.show()
|
||||||
|
|
||||||
# Get gear names from loadout selection if available, otherwise fall back to selected weapon
|
# Get gear names and costs from simplified loadout structure
|
||||||
weapon_name = getattr(self, '_session_weapon_name', None) or self._selected_weapon or "Unknown"
|
session_display = getattr(self, '_session_display', {})
|
||||||
armor_name = getattr(self, '_session_armor_name', None) or "None"
|
session_costs = getattr(self, '_session_costs', {})
|
||||||
healing_name = getattr(self, '_session_healing_name', None) or "None"
|
|
||||||
|
weapon_name = session_display.get('weapon_name', self._selected_weapon or "Unknown")
|
||||||
|
armor_name = session_display.get('armor_name', "None")
|
||||||
|
healing_name = session_display.get('healing_name', "None")
|
||||||
|
loadout_name = "Loadout" if session_costs else "Default"
|
||||||
|
|
||||||
weapon_stats = self._selected_weapon_stats or {}
|
weapon_stats = self._selected_weapon_stats or {}
|
||||||
weapon_dpp = Decimal(str(weapon_stats.get('dpp', 0)))
|
weapon_dpp = Decimal(str(weapon_stats.get('dpp', 0)))
|
||||||
weapon_cost_per_hour = Decimal(str(weapon_stats.get('cost_per_hour', 0)))
|
weapon_cost_per_hour = Decimal(str(weapon_stats.get('cost_per_hour', 0)))
|
||||||
|
|
||||||
# Get loadout name from session selection
|
# Get cost data from simplified structure
|
||||||
loadout_info = getattr(self, '_session_loadout_info', None)
|
cost_per_shot = session_costs.get('cost_per_shot', Decimal('0'))
|
||||||
loadout_name = loadout_info.get('name', 'Default') if loadout_info else "Default"
|
cost_per_hit = session_costs.get('cost_per_hit', Decimal('0'))
|
||||||
|
cost_per_heal = session_costs.get('cost_per_heal', Decimal('0'))
|
||||||
# Get cost data from session loadout
|
|
||||||
cost_per_shot = getattr(self, '_session_cost_per_shot', Decimal('0'))
|
|
||||||
cost_per_hit = getattr(self, '_session_cost_per_hit', Decimal('0'))
|
|
||||||
cost_per_heal = getattr(self, '_session_cost_per_heal', Decimal('0'))
|
|
||||||
|
|
||||||
self.hud.start_session(
|
self.hud.start_session(
|
||||||
weapon=weapon_name,
|
weapon=weapon_name,
|
||||||
|
|
@ -966,12 +966,8 @@ class MainWindow(QMainWindow):
|
||||||
cost_per_heal=cost_per_heal
|
cost_per_heal=cost_per_heal
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set up cost tracker if loadout selected (database-based only)
|
# Simple cost tracking - no database required
|
||||||
if loadout_info and loadout_info.get('id') and loadout_info.get('source') == 'db':
|
self.log_info("CostTracker", "Cost tracking enabled with pre-calculated values")
|
||||||
self._setup_session_cost_tracker(loadout_info)
|
|
||||||
else:
|
|
||||||
# For JSON-based loadouts, use manual cost tracking based on extracted values
|
|
||||||
self.log_info("CostTracker", "Using manual cost tracking for file-based loadout")
|
|
||||||
|
|
||||||
self.log_info("HUD", f"HUD shown - Weapon: {weapon_name}, Armor: {armor_name}, Healing: {healing_name}, Loadout: {loadout_name}")
|
self.log_info("HUD", f"HUD shown - Weapon: {weapon_name}, Armor: {armor_name}, Healing: {healing_name}, Loadout: {loadout_name}")
|
||||||
|
|
||||||
|
|
@ -1239,45 +1235,41 @@ class MainWindow(QMainWindow):
|
||||||
"""Handle start session button - shows loadout selection first."""
|
"""Handle start session button - shows loadout selection first."""
|
||||||
if self.current_project and self.session_state == SessionState.IDLE:
|
if self.current_project and self.session_state == SessionState.IDLE:
|
||||||
# Show loadout selection dialog
|
# Show loadout selection dialog
|
||||||
from ui.loadout_selection_dialog import LoadoutSelectionDialog
|
from ui.loadout_selection_dialog_simple import LoadoutSelectionDialog
|
||||||
dialog = LoadoutSelectionDialog(self)
|
dialog = LoadoutSelectionDialog(self)
|
||||||
dialog.loadout_selected.connect(self._on_loadout_selected_for_session)
|
dialog.loadout_selected.connect(self._on_loadout_selected_for_session)
|
||||||
dialog.rejected.connect(lambda: self.log_info("Session", "Session start cancelled - no loadout selected"))
|
dialog.rejected.connect(lambda: self.log_info("Session", "Session start cancelled - no loadout selected"))
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|
||||||
def _on_loadout_selected_for_session(self, loadout_info: dict):
|
def _on_loadout_selected_for_session(self, loadout_info: dict):
|
||||||
"""Handle loadout selection and start session."""
|
"""Handle loadout selection and start session - simplified cost-focused version."""
|
||||||
loadout_id = loadout_info.get('id', 0)
|
|
||||||
loadout_name = loadout_info.get('name', 'No Loadout')
|
loadout_name = loadout_info.get('name', 'No Loadout')
|
||||||
loadout_data = loadout_info.get('data', {})
|
costs = loadout_info.get('costs', {})
|
||||||
|
display = loadout_info.get('display', {})
|
||||||
|
|
||||||
if loadout_id > 0:
|
# Store cost data for session tracking
|
||||||
self.log_info("Session", f"Starting session with loadout: {loadout_name} (ID: {loadout_id})")
|
from decimal import Decimal
|
||||||
# Store the selected loadout info for use in start_session
|
self._session_costs = {
|
||||||
self._session_loadout_info = loadout_info
|
'cost_per_shot': costs.get('cost_per_shot', Decimal('0')),
|
||||||
|
'cost_per_hit': costs.get('cost_per_hit', Decimal('0')),
|
||||||
|
'cost_per_heal': costs.get('cost_per_heal', Decimal('0')),
|
||||||
|
}
|
||||||
|
|
||||||
# Extract gear names from loadout data
|
# Store display data for HUD
|
||||||
self._session_weapon_name = loadout_data.get('weapon_name', 'Unknown')
|
self._session_display = {
|
||||||
self._session_armor_name = loadout_data.get('armor_set_name', 'Unknown')
|
'weapon_name': display.get('weapon_name', 'None'),
|
||||||
self._session_healing_name = loadout_data.get('heal_name', 'Unknown')
|
'armor_name': display.get('armor_name', 'None'),
|
||||||
|
'healing_name': display.get('healing_name', 'None'),
|
||||||
|
}
|
||||||
|
|
||||||
# Extract cost data for session tracking (even for JSON-based loadouts)
|
if any(self._session_costs.values()):
|
||||||
from decimal import Decimal
|
self.log_info("Session", f"Starting with loadout: {loadout_name}")
|
||||||
self._session_cost_per_shot = Decimal(str(loadout_data.get('weapon_decay_pec', 0))) / Decimal('100') + \
|
self.log_info("SessionCosts",
|
||||||
Decimal(str(loadout_data.get('weapon_ammo_pec', 0))) * Decimal('0.0001')
|
f"Shot: {self._session_costs['cost_per_shot']:.4f} PED, "
|
||||||
self._session_cost_per_hit = Decimal(str(loadout_data.get('armor_decay_pec', 0))) / Decimal('100')
|
f"Hit: {self._session_costs['cost_per_hit']:.4f} PED, "
|
||||||
self._session_cost_per_heal = Decimal(str(loadout_data.get('heal_cost_pec', 0))) / Decimal('100')
|
f"Heal: {self._session_costs['cost_per_heal']:.4f} PED")
|
||||||
|
|
||||||
self.log_info("SessionCosts", f"Cost/Shot: {self._session_cost_per_shot:.4f}, Cost/Hit: {self._session_cost_per_hit:.4f}, Cost/Heal: {self._session_cost_per_heal:.4f}")
|
|
||||||
else:
|
else:
|
||||||
self.log_info("Session", "Starting session without loadout")
|
self.log_info("Session", f"Starting with loadout: {loadout_name} (no costs configured)")
|
||||||
self._session_loadout_info = None
|
|
||||||
self._session_weapon_name = None
|
|
||||||
self._session_armor_name = None
|
|
||||||
self._session_healing_name = None
|
|
||||||
self._session_cost_per_shot = None
|
|
||||||
self._session_cost_per_hit = None
|
|
||||||
self._session_cost_per_heal = None
|
|
||||||
|
|
||||||
# Now start the session
|
# Now start the session
|
||||||
if self.current_project:
|
if self.current_project:
|
||||||
|
|
@ -1462,25 +1454,37 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def on_loadout_manager(self):
|
def on_loadout_manager(self):
|
||||||
"""Open Loadout Manager dialog."""
|
"""Open Loadout Manager dialog."""
|
||||||
from ui.loadout_manager import LoadoutManagerDialog
|
from ui.loadout_manager_simple import LoadoutManagerDialog
|
||||||
dialog = LoadoutManagerDialog(self)
|
dialog = LoadoutManagerDialog(self)
|
||||||
dialog.loadout_saved.connect(self.on_loadout_selected)
|
dialog.loadout_saved.connect(self.on_loadout_selected)
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|
||||||
def on_loadout_selected(self, loadout):
|
def on_loadout_selected(self, loadout):
|
||||||
"""Handle loadout selection from Loadout Manager."""
|
"""Handle loadout selection from Loadout Manager - simplified version."""
|
||||||
self._selected_loadout = loadout
|
self._selected_loadout = loadout
|
||||||
self.log_info("Loadout", f"Selected loadout: {loadout.name}")
|
self.log_info("Loadout", f"Selected loadout: {loadout.name}")
|
||||||
|
|
||||||
# Update selected gear from loadout
|
# Update selected gear from loadout (simplified structure)
|
||||||
if hasattr(loadout, 'weapon_name'):
|
if hasattr(loadout, 'weapon_name'):
|
||||||
self._selected_weapon = loadout.weapon_name
|
self._selected_weapon = loadout.weapon_name
|
||||||
if hasattr(loadout, 'heal_cost_pec'):
|
if hasattr(loadout, 'healing_name'):
|
||||||
# Create medical tool stats from loadout heal cost
|
self._selected_medical_tool = loadout.healing_name
|
||||||
self._selected_medical_tool = loadout.heal_name
|
|
||||||
self._selected_medical_tool_stats = {
|
self._selected_medical_tool_stats = {
|
||||||
'decay': float(loadout.heal_cost_pec),
|
'decay': float(loadout.healing_decay_pec),
|
||||||
'cost_per_heal': float(loadout.heal_cost_pec) / 100.0, # Convert PEC to PED
|
'cost_per_heal': float(loadout.healing_cost_per_heal),
|
||||||
|
}
|
||||||
|
# Store simplified costs for session
|
||||||
|
if hasattr(loadout, 'weapon_cost_per_shot'):
|
||||||
|
self._session_costs = {
|
||||||
|
'cost_per_shot': loadout.weapon_cost_per_shot,
|
||||||
|
'cost_per_hit': loadout.armor_cost_per_hit,
|
||||||
|
'cost_per_heal': loadout.healing_cost_per_heal,
|
||||||
|
}
|
||||||
|
if hasattr(loadout, 'weapon_name'):
|
||||||
|
self._session_display = {
|
||||||
|
'weapon_name': loadout.weapon_name,
|
||||||
|
'armor_name': loadout.armor_name,
|
||||||
|
'healing_name': loadout.healing_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
def on_select_gear(self, gear_type: str = "weapon"):
|
def on_select_gear(self, gear_type: str = "weapon"):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue