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
This commit is contained in:
LemonNexus 2026-02-09 22:06:31 +00:00
parent f6ec57d2a2
commit 3ae25503d6
1 changed files with 97 additions and 44 deletions

View File

@ -111,29 +111,42 @@ class WeaponSelectorDialog(QDialog):
self._weapons = [] self._weapons = []
for w in all_weapons: for w in all_weapons:
try: try:
# Validate that decay and ammo are valid numbers # Get raw values
decay_val = w.decay if w.decay is not None else 0 decay_raw = w.decay
# NexusWeapon uses ammo_burn, not ammo ammo_raw = w.ammo_burn
ammo_val = w.ammo_burn if w.ammo_burn is not None else 0
# Additional validation - skip if values are empty strings or 'null' # Log problematic values for debugging
if decay_val == '' or decay_val == 'null': if decay_raw is None or str(decay_raw).strip() == '':
decay_val = 0 logger.debug(f"Weapon {w.name}: decay is None or empty")
if ammo_val == '' or ammo_val == 'null': decay_raw = 0
ammo_val = 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) self._weapons.append(w)
except (InvalidOperation, ValueError, TypeError) as e: except (InvalidOperation, ValueError, TypeError) as e:
# Skip weapons with invalid data # Log the exact problem
logger.debug(f"Skipping weapon {getattr(w, 'name', 'unknown')} due to invalid decay/ammo: {e}") 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 continue
# Sort by name # Sort by name
self._weapons.sort(key=lambda w: w.name.lower()) 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) self._populate_list(self._weapons)
except Exception as e: except Exception as e:
logger.error(f"Failed to load weapons: {e}") logger.error(f"Failed to load weapons: {e}")
@ -145,32 +158,57 @@ class WeaponSelectorDialog(QDialog):
for weapon in weapons: for weapon in weapons:
try: 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 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 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 # Calculate cost per shot
decay_pec = Decimal(str(decay_raw)) decay_pec = Decimal(decay_str)
ammo = Decimal(str(ammo_raw)) ammo = Decimal(ammo_str)
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001")) cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
item = QListWidgetItem(f"{weapon.name} (💰 {cost_per_shot:.4f} PED)") item = QListWidgetItem(f"{weapon.name} (💰 {cost_per_shot:.4f} PED)")
item.setData(Qt.ItemDataRole.UserRole, weapon) item.setData(Qt.ItemDataRole.UserRole, weapon)
# Tooltip # Safe tooltip formatting
tooltip = ( try:
f"Damage: {weapon.damage}\n" damage_str = str(weapon.damage) if weapon.damage is not None else "-"
f"Decay: {decay_raw} PEC\n" range_str = str(weapon.range_val) if weapon.range_val is not None else "-"
f"Ammo: {ammo_raw}\n"
f"Range: {weapon.range_val}\n" # Format DPP safely
f"DPP: {weapon.dpp:.2f}" if weapon.dpp else "DPP: -" dpp_str = "-"
) if weapon.dpp is not None:
item.setToolTip(tooltip) 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) self.weapon_list.addItem(item)
except Exception as e: except Exception as e:
# Skip weapons that fail to process # 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 continue
def _on_search(self, text): def _on_search(self, text):
@ -191,23 +229,38 @@ class WeaponSelectorDialog(QDialog):
self.selected_weapon = weapon self.selected_weapon = weapon
# Get values with safe defaults try:
decay_raw = weapon.decay if weapon.decay is not None else 0 # Get values with safe defaults and validation
ammo_raw = weapon.ammo_burn if weapon.ammo_burn is not None else 0 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)) # Ensure they're valid strings for Decimal conversion
ammo = Decimal(str(ammo_raw)) decay_str = str(decay_raw).strip() if decay_raw else '0'
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001")) ammo_str = str(ammo_raw).strip() if ammo_raw else '0'
# Update preview if decay_str.lower() in ('null', 'none', ''):
self.preview_name.setText(weapon.name) decay_str = '0'
self.preview_damage.setText(str(weapon.damage) if weapon.damage is not None else "-") if ammo_str.lower() in ('null', 'none', ''):
self.preview_decay.setText(f"{decay_raw} PEC") ammo_str = '0'
self.preview_ammo.setText(str(ammo_raw))
self.preview_cost.setText(f"{cost_per_shot:.4f} PED") # Calculate cost per shot
decay_pec = Decimal(decay_str)
self.ok_btn.setEnabled(True) 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 # Calculate cost per shot
decay_pec = Decimal(str(weapon.decay)) decay_pec = Decimal(str(weapon.decay))