Lemontropia-Suite/ui/weapon_selector.py

273 lines
11 KiB
Python

"""
Lemontropia Suite - Simple Weapon Selector
Quick weapon selection for cost configuration.
"""
import logging
from decimal import Decimal, InvalidOperation
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QLineEdit, QListWidget, QListWidgetItem,
QMessageBox, QFormLayout, QGroupBox
)
from PyQt6.QtCore import Qt, pyqtSignal
from core.nexus_full_api import get_nexus_api
logger = logging.getLogger(__name__)
class WeaponSelectorDialog(QDialog):
"""Simple dialog to select a weapon for cost tracking."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Select Weapon")
self.setMinimumSize(500, 400)
self.selected_weapon = None
self._weapons = []
self._setup_ui()
self._load_weapons()
def _setup_ui(self):
"""Setup simple UI."""
layout = QVBoxLayout(self)
layout.setSpacing(10)
# Search
search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:"))
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("Type weapon name...")
self.search_edit.textChanged.connect(self._on_search)
search_layout.addWidget(self.search_edit)
layout.addLayout(search_layout)
# Weapon list
self.weapon_list = QListWidget()
self.weapon_list.itemClicked.connect(self._on_select)
self.weapon_list.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(self.weapon_list)
# Preview
preview_group = QGroupBox("Selected Weapon")
preview_layout = QFormLayout(preview_group)
self.preview_name = QLabel("None")
preview_layout.addRow("Name:", self.preview_name)
self.preview_damage = QLabel("-")
preview_layout.addRow("Damage:", self.preview_damage)
self.preview_decay = QLabel("-")
preview_layout.addRow("Decay:", self.preview_decay)
self.preview_ammo = QLabel("-")
preview_layout.addRow("Ammo:", self.preview_ammo)
self.preview_cost = QLabel("-")
self.preview_cost.setStyleSheet("font-weight: bold; color: #7FFF7F;")
preview_layout.addRow("Cost/Shot:", self.preview_cost)
layout.addWidget(preview_group)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
self.ok_btn = QPushButton("Select")
self.ok_btn.clicked.connect(self.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)
cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
def _load_weapons(self):
"""Load weapons from API."""
try:
nexus = get_nexus_api()
all_weapons = nexus.get_all_weapons()
# Filter out weapons with invalid/null decay or ammo
self._weapons = []
for w in all_weapons:
try:
# Get raw values
decay_raw = w.decay
ammo_raw = w.ammo_burn
# Log problematic values for debugging
if decay_raw is None or str(decay_raw).strip() == '':
logger.debug(f"Weapon {w.name}: decay is None or empty")
decay_raw = 0
if ammo_raw is None or str(ammo_raw).strip() == '':
logger.debug(f"Weapon {w.name}: ammo_burn is None or empty")
ammo_raw = 0
# Convert to string and strip
decay_str = str(decay_raw).strip()
ammo_str = str(ammo_raw).strip()
# Handle 'null' or 'None' strings
if decay_str.lower() in ('null', 'none', ''):
decay_str = '0'
if ammo_str.lower() in ('null', 'none', ''):
ammo_str = '0'
# Try to convert to Decimal
decay_val = Decimal(decay_str)
ammo_val = Decimal(ammo_str)
self._weapons.append(w)
except (InvalidOperation, ValueError, TypeError) as e:
# Log the exact problem
logger.warning(f"Skipping weapon '{getattr(w, 'name', 'unknown')}': decay={getattr(w, 'decay', 'N/A')}, ammo_burn={getattr(w, 'ammo_burn', 'N/A')} - Error: {e}")
continue
# Sort by name
self._weapons.sort(key=lambda w: w.name.lower())
logger.info(f"Loaded {len(self._weapons)} valid weapons out of {len(all_weapons)} total")
self._populate_list(self._weapons)
except Exception as e:
logger.error(f"Failed to load weapons: {e}")
QMessageBox.warning(self, "Error", f"Failed to load weapons: {e}")
def _populate_list(self, weapons):
"""Populate list with weapons."""
self.weapon_list.clear()
for weapon in weapons:
try:
# Get values with safe defaults and validation
decay_raw = weapon.decay if weapon.decay is not None else 0
ammo_raw = weapon.ammo_burn if weapon.ammo_burn is not None else 0
# Ensure they're valid strings for Decimal conversion
decay_str = str(decay_raw).strip() if decay_raw else '0'
ammo_str = str(ammo_raw).strip() if ammo_raw else '0'
if decay_str.lower() in ('null', 'none', ''):
decay_str = '0'
if ammo_str.lower() in ('null', 'none', ''):
ammo_str = '0'
# Calculate cost per shot
decay_pec = Decimal(decay_str)
ammo = Decimal(ammo_str)
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
item = QListWidgetItem(f"{weapon.name} (💰 {cost_per_shot:.4f} PED)")
item.setData(Qt.ItemDataRole.UserRole, weapon)
# Safe tooltip formatting
try:
damage_str = str(weapon.damage) if weapon.damage is not None else "-"
range_str = str(weapon.range_val) if weapon.range_val is not None else "-"
# Format DPP safely
dpp_str = "-"
if weapon.dpp is not None:
try:
dpp_val = Decimal(str(weapon.dpp))
dpp_str = f"{dpp_val:.2f}"
except:
dpp_str = str(weapon.dpp)
tooltip = (
f"Damage: {damage_str}\n"
f"Decay: {decay_str} PEC\n"
f"Ammo: {ammo_str}\n"
f"Range: {range_str}\n"
f"DPP: {dpp_str}"
)
item.setToolTip(tooltip)
except Exception as tooltip_error:
logger.debug(f"Tooltip error for {weapon.name}: {tooltip_error}")
item.setToolTip(f"{weapon.name}")
self.weapon_list.addItem(item)
except Exception as e:
# Skip weapons that fail to process
logger.warning(f"Skipping weapon in populate_list {getattr(weapon, 'name', 'unknown')}: {e}")
continue
def _on_search(self, text):
"""Filter weapons by search text."""
if not text:
self._populate_list(self._weapons)
return
text_lower = text.lower()
filtered = [w for w in self._weapons if text_lower in w.name.lower()]
self._populate_list(filtered)
def _on_select(self, item):
"""Update preview when weapon selected."""
weapon = item.data(Qt.ItemDataRole.UserRole)
if not weapon:
return
self.selected_weapon = weapon
try:
# Get values with safe defaults and validation
decay_raw = weapon.decay if weapon.decay is not None else 0
ammo_raw = weapon.ammo_burn if weapon.ammo_burn is not None else 0
# Ensure they're valid strings for Decimal conversion
decay_str = str(decay_raw).strip() if decay_raw else '0'
ammo_str = str(ammo_raw).strip() if ammo_raw else '0'
if decay_str.lower() in ('null', 'none', ''):
decay_str = '0'
if ammo_str.lower() in ('null', 'none', ''):
ammo_str = '0'
# Calculate cost per shot
decay_pec = Decimal(decay_str)
ammo = Decimal(ammo_str)
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
# Update preview with safe string conversion
self.preview_name.setText(str(weapon.name) if weapon.name else "Unknown")
self.preview_damage.setText(str(weapon.damage) if weapon.damage is not None else "-")
self.preview_decay.setText(f"{decay_str} PEC")
self.preview_ammo.setText(str(ammo_str))
self.preview_cost.setText(f"{cost_per_shot:.4f} PED")
self.ok_btn.setEnabled(True)
except Exception as e:
logger.error(f"Error updating preview for {getattr(weapon, 'name', 'unknown')}: {e}")
self.preview_name.setText(str(weapon.name) if weapon.name else "Unknown")
self.preview_cost.setText("Error")
self.ok_btn.setEnabled(False)
def _on_double_click(self, item):
"""Double-click to select immediately."""
self._on_select(item)
self.accept()
def get_selected_weapon(self):
"""Get the selected weapon."""
return self.selected_weapon