From 3ae25503d618cc1463af40ede833a1aec9c8235a Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Mon, 9 Feb 2026 22:06:31 +0000 Subject: [PATCH] fix: comprehensive null/invalid value handling in weapon selector - Add detailed logging for skipped weapons - Handle 'null', 'None', empty strings in decay/ammo values - Safe Decimal conversion with try/except blocks - Safe tooltip formatting for all weapon fields - Safe preview update with error handling --- ui/weapon_selector.py | 141 +++++++++++++++++++++++++++++------------- 1 file changed, 97 insertions(+), 44 deletions(-) diff --git a/ui/weapon_selector.py b/ui/weapon_selector.py index 913e9a8..944d96b 100644 --- a/ui/weapon_selector.py +++ b/ui/weapon_selector.py @@ -111,29 +111,42 @@ class WeaponSelectorDialog(QDialog): self._weapons = [] for w in all_weapons: try: - # Validate that decay and ammo are valid numbers - decay_val = w.decay if w.decay is not None else 0 - # NexusWeapon uses ammo_burn, not ammo - ammo_val = w.ammo_burn if w.ammo_burn is not None else 0 + # Get raw values + decay_raw = w.decay + ammo_raw = w.ammo_burn - # Additional validation - skip if values are empty strings or 'null' - if decay_val == '' or decay_val == 'null': - decay_val = 0 - if ammo_val == '' or ammo_val == 'null': - ammo_val = 0 + # 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) - # Try to convert to Decimal to validate - Decimal(str(decay_val)) - Decimal(str(ammo_val)) self._weapons.append(w) except (InvalidOperation, ValueError, TypeError) as e: - # Skip weapons with invalid data - logger.debug(f"Skipping weapon {getattr(w, 'name', 'unknown')} due to invalid decay/ammo: {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}") @@ -145,32 +158,57 @@ class WeaponSelectorDialog(QDialog): for weapon in weapons: try: - # Get values with safe defaults + # 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(str(decay_raw)) - ammo = Decimal(str(ammo_raw)) + 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) - # Tooltip - tooltip = ( - f"Damage: {weapon.damage}\n" - f"Decay: {decay_raw} PEC\n" - f"Ammo: {ammo_raw}\n" - f"Range: {weapon.range_val}\n" - f"DPP: {weapon.dpp:.2f}" if weapon.dpp else "DPP: -" - ) - item.setToolTip(tooltip) + # 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.debug(f"Skipping weapon {getattr(weapon, 'name', 'unknown')}: {e}") + logger.warning(f"Skipping weapon in populate_list {getattr(weapon, 'name', 'unknown')}: {e}") continue def _on_search(self, text): @@ -191,23 +229,38 @@ class WeaponSelectorDialog(QDialog): self.selected_weapon = weapon - # Get values with safe defaults - 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 - - # Calculate cost per shot - decay_pec = Decimal(str(decay_raw)) - ammo = Decimal(str(ammo_raw)) - cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001")) - - # Update preview - self.preview_name.setText(weapon.name) - self.preview_damage.setText(str(weapon.damage) if weapon.damage is not None else "-") - self.preview_decay.setText(f"{decay_raw} PEC") - self.preview_ammo.setText(str(ammo_raw)) - self.preview_cost.setText(f"{cost_per_shot:.4f} PED") - - self.ok_btn.setEnabled(True) + 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) # Calculate cost per shot decay_pec = Decimal(str(weapon.decay))