Lemontropia-Suite/ui/loadout_selection_dialog.py

357 lines
14 KiB
Python

# Description: Loadout selection dialog for hunting sessions
# Allows choosing a loadout when starting a new session
# Standards: Python 3.11+, PyQt6, type hints
import json
from pathlib import Path
from decimal import Decimal
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QListWidget, QListWidgetItem, QDialogButtonBox, QGroupBox,
QFormLayout, QMessageBox
)
from PyQt6.QtCore import Qt, pyqtSignal
from typing import Optional, Dict, Any, List
from core.loadout_db import LoadoutDatabase
from core.database import DatabaseManager
class LoadoutSelectionDialog(QDialog):
"""
Dialog for selecting a loadout when starting a hunting session.
Shows saved loadouts with their per-action cost metrics.
"""
loadout_selected = pyqtSignal(dict) # loadout_info dict with id, name, source, data
def __init__(self, parent=None, db_manager: Optional[DatabaseManager] = None):
"""
Initialize loadout selection dialog.
Args:
parent: Parent widget
db_manager: Optional database manager
"""
super().__init__(parent)
self.setWindowTitle("Select Loadout - Hunting Session")
self.setMinimumSize(500, 400)
self.db = db_manager or DatabaseManager()
self.loadout_db = LoadoutDatabase(self.db)
# Also check JSON files
self.config_dir = Path.home() / ".lemontropia" / "loadouts"
self.config_dir.mkdir(parents=True, exist_ok=True)
self.selected_loadout_id: Optional[int] = None
self.selected_loadout_name: Optional[str] = None
self._setup_ui()
self._load_loadouts()
def _setup_ui(self):
"""Setup the dialog UI."""
layout = QVBoxLayout(self)
layout.setSpacing(10)
# Header
header = QLabel("⚙️ Select Your Hunting Loadout")
header.setStyleSheet("font-size: 16px; font-weight: bold; color: #FFD700;")
layout.addWidget(header)
description = QLabel(
"Choose a loadout to track your costs accurately during this hunting session.\n"
"Costs will be calculated based on your configured gear."
)
description.setStyleSheet("color: #888888;")
description.setWordWrap(True)
layout.addWidget(description)
# Loadout list
self.loadout_list = QListWidget()
self.loadout_list.setAlternatingRowColors(True)
self.loadout_list.itemSelectionChanged.connect(self._on_selection_changed)
self.loadout_list.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(self.loadout_list)
# Preview panel
self.preview_group = QGroupBox("Loadout Details")
preview_layout = QFormLayout(self.preview_group)
self.preview_weapon = QLabel("-")
self.preview_armor = QLabel("-")
self.preview_healing = QLabel("-")
self.preview_cost_shot = QLabel("-")
self.preview_cost_hit = QLabel("-")
self.preview_cost_heal = QLabel("-")
preview_layout.addRow("🗡️ Weapon:", self.preview_weapon)
preview_layout.addRow("🛡️ Armor:", self.preview_armor)
preview_layout.addRow("💚 Healing:", self.preview_healing)
preview_layout.addRow("💰 Cost/Shot:", self.preview_cost_shot)
preview_layout.addRow("💰 Cost/Hit:", self.preview_cost_hit)
preview_layout.addRow("💰 Cost/Heal:", self.preview_cost_heal)
layout.addWidget(self.preview_group)
# Buttons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self._on_accept)
buttons.rejected.connect(self.reject)
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
self.ok_button.setText("Start Session")
self.ok_button.setEnabled(False)
# Add "No Loadout" option button
no_loadout_btn = QPushButton("Skip (No Loadout)")
no_loadout_btn.setToolTip("Start session without cost tracking")
no_loadout_btn.clicked.connect(self._on_no_loadout)
btn_layout = QHBoxLayout()
btn_layout.addWidget(no_loadout_btn)
btn_layout.addStretch()
btn_layout.addWidget(buttons)
layout.addLayout(btn_layout)
def _load_loadouts(self):
"""Load saved loadouts from database and JSON files."""
self.all_loadouts = [] # Store all loadouts for lookup
# First try database
db_loadouts = self.loadout_db.list_loadouts()
for loadout in db_loadouts:
self.all_loadouts.append({
'id': loadout['id'],
'name': loadout['name'],
'source': 'db',
'data': loadout
})
# Also check JSON files
try:
for filepath in sorted(self.config_dir.glob("*.json")):
try:
with open(filepath, 'r') as f:
data = json.load(f)
# Generate a unique ID based on filename
loadout_id = abs(hash(filepath.stem)) % (10 ** 9)
self.all_loadouts.append({
'id': loadout_id,
'name': data.get('name', filepath.stem),
'source': 'json',
'filepath': str(filepath),
'data': data
})
except Exception as e:
print(f"Error loading {filepath}: {e}")
except Exception as e:
print(f"Error scanning config dir: {e}")
if not self.all_loadouts:
# No loadouts saved
item = QListWidgetItem("No loadouts saved yet")
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
item.setForeground(Qt.GlobalColor.gray)
self.loadout_list.addItem(item)
return
# Sort by name
self.all_loadouts.sort(key=lambda x: x['name'].lower())
for loadout_info in self.all_loadouts:
item = QListWidgetItem()
item.setText(loadout_info['name'])
data = loadout_info['data']
# Build tooltip with gear info
if loadout_info['source'] == 'db':
tooltip = f"Weapon: {data.get('weapon_name') or 'None'}\n"
tooltip += f"Armor: {data.get('armor_name') or 'None'}\n"
tooltip += f"Healing: {data.get('healing_tool_name') or 'None'}\n"
tooltip += f"Cost/Shot: {data.get('cost_per_shot_ped', 0):.4f} PED\n"
tooltip += f"Cost/Hit: {data.get('cost_per_hit_ped', 0):.4f} PED\n"
tooltip += f"Cost/Heal: {data.get('cost_per_heal_ped', 0):.4f} PED"
else:
# JSON format
tooltip = f"Weapon: {data.get('weapon_name', 'None')}\n"
tooltip += f"Armor: {data.get('armor_set_name', 'None')}\n"
tooltip += f"Healing: {data.get('heal_name', 'None')}"
item.setToolTip(tooltip)
# Store loadout info
item.setData(Qt.ItemDataRole.UserRole, loadout_info['id'])
self.loadout_list.addItem(item)
def _on_selection_changed(self):
"""Handle loadout selection change."""
items = self.loadout_list.selectedItems()
if not items:
self.ok_button.setEnabled(False)
self._clear_preview()
return
item = items[0]
loadout_id = item.data(Qt.ItemDataRole.UserRole)
if loadout_id is None:
self.ok_button.setEnabled(False)
return
# Find the loadout info
loadout_info = None
for lo in self.all_loadouts:
if lo['id'] == loadout_id:
loadout_info = lo
break
if not loadout_info:
self.ok_button.setEnabled(False)
return
self.selected_loadout_id = loadout_id
self.selected_loadout_name = loadout_info['name']
# Load and display preview
self._update_preview(loadout_info)
self.ok_button.setEnabled(True)
def _update_preview(self, loadout_info: Dict[str, Any]):
"""Update preview panel with loadout details."""
data = loadout_info['data']
if loadout_info['source'] == 'db':
self.preview_weapon.setText(data.get('weapon_name') or "None")
self.preview_armor.setText(data.get('armor_name') or "None")
self.preview_healing.setText(data.get('healing_tool_name') or "None")
# Format costs
cost_shot = Decimal(str(data.get('cost_per_shot_ped', 0)))
cost_hit = Decimal(str(data.get('cost_per_hit_ped', 0)))
cost_heal = Decimal(str(data.get('cost_per_heal_ped', 0)))
else:
# JSON format - values stored in PEC, need to convert to PED
self.preview_weapon.setText(data.get('weapon_name', "None"))
self.preview_armor.setText(data.get('armor_set_name', "None"))
self.preview_healing.setText(data.get('heal_name', "None"))
# Try to calculate costs from JSON data
# weapon_decay_pec is in PEC (divide by 100 to get PED)
# weapon_ammo_pec is ammo count (multiply by 0.0001 to get PED)
weapon_decay_raw = data.get('weapon_decay_pec', 0)
weapon_ammo_raw = data.get('weapon_ammo_pec', 0)
weapon_decay = Decimal(str(weapon_decay_raw)) / Decimal("100") # PEC to PED
weapon_ammo = Decimal(str(weapon_ammo_raw)) * Decimal("0.0001") # Ammo to PED
cost_shot = weapon_decay + weapon_ammo
print(f"DEBUG: weapon_decay_pec={weapon_decay_raw} -> {weapon_decay} PED")
print(f"DEBUG: weapon_ammo_pec={weapon_ammo_raw} -> {weapon_ammo} PED")
# Armor decay per hit (in PEC, convert to PED)
armor_decay_raw = data.get('armor_decay_pec', 0)
armor_decay = Decimal(str(armor_decay_raw)) / Decimal("100") # PEC to PED
cost_hit = armor_decay
# Healing cost per use (in PEC, convert to PED)
heal_cost_raw = data.get('heal_cost_pec', 0)
heal_cost = Decimal(str(heal_cost_raw)) / Decimal("100") # PEC to PED
cost_heal = heal_cost
self.preview_cost_shot.setText(f"{cost_shot:.4f} PED")
self.preview_cost_hit.setText(f"{cost_hit:.4f} PED")
self.preview_cost_heal.setText(f"{cost_heal:.4f} PED")
# Color code based on cost
if cost_shot > Decimal("0.5"):
self.preview_cost_shot.setStyleSheet("color: #FF7F7F;")
else:
self.preview_cost_shot.setStyleSheet("color: #7FFF7F;")
def _clear_preview(self):
"""Clear preview panel."""
self.preview_weapon.setText("-")
self.preview_armor.setText("-")
self.preview_healing.setText("-")
self.preview_cost_shot.setText("-")
self.preview_cost_hit.setText("-")
self.preview_cost_heal.setText("-")
def _on_double_click(self, item: QListWidgetItem):
"""Handle double click on loadout."""
if item.data(Qt.ItemDataRole.UserRole):
self._on_accept()
def _on_accept(self):
"""Handle OK button - start session with selected loadout."""
if self.selected_loadout_id:
# Find the full loadout info
loadout_info = None
for lo in self.all_loadouts:
if lo['id'] == self.selected_loadout_id:
loadout_info = lo
break
if loadout_info:
self.loadout_selected.emit(loadout_info)
else:
# Fallback
self.loadout_selected.emit({
'id': self.selected_loadout_id,
'name': self.selected_loadout_name or "Unknown",
'source': 'unknown',
'data': {}
})
self.accept()
def _on_no_loadout(self):
"""Handle skip button - start session without loadout."""
reply = QMessageBox.question(
self,
"No Loadout Selected",
"Start session without cost tracking?\n\n"
"You won't be able to track accurate weapon/armor/healing costs.",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.loadout_selected.emit({
'id': 0,
'name': 'No Loadout',
'source': 'none',
'data': {}
})
self.accept()
# Main entry for testing
if __name__ == "__main__":
import sys
import logging
from PyQt6.QtWidgets import QApplication
logging.basicConfig(level=logging.INFO)
app = QApplication(sys.argv)
app.setStyle('Fusion')
dialog = LoadoutSelectionDialog()
# Connect signal for testing
dialog.loadout_selected.connect(
lambda id, name: print(f"Selected loadout: {name} (ID: {id})")
)
if dialog.exec() == QDialog.DialogCode.Accepted:
print("Session starting!")
sys.exit(0)