""" 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