fix(api): correct attachment field parsing for all attachment types

- Fixed NexusAttachment.from_api() to parse correct API structure:
  - Amplifiers: Damage values from Properties.Damage
  - Scopes: Skill bonuses from Properties.SkillModification/SkillBonus, zoom from Properties.Zoom
  - Absorbers: Absorption from Economy.Absorption
- Added zoom and absorption fields to NexusAttachment
- Updated attachment selector UI to show type-specific columns
- Added zoom and absorption to preview panel
This commit is contained in:
LemonNexus 2026-02-09 17:12:44 +00:00
parent 4ef03d96c8
commit e8f0d7860e
3 changed files with 161 additions and 55 deletions

View File

@ -153,62 +153,58 @@ class NexusAttachment(NexusItem):
range_bonus: Decimal
decay: Decimal
efficiency_bonus: Decimal
zoom: int = 0 # For scopes
absorption: Decimal = Decimal("0") # For absorbers
@classmethod
def from_api(cls, data: Dict[str, Any]) -> "NexusAttachment":
"""Create from API response."""
props = data.get('Properties', {})
# Debug: Log available properties
logger.debug(f"Attachment {data.get('Name')} properties: {props.keys()}")
# Try multiple possible field names for each stat
# Damage bonus might be in Damage, DamageBonus, or DamageIncrease
damage_bonus = Decimal("0")
for key in ['Damage', 'DamageBonus', 'DamageIncrease', 'dmg', 'damage']:
if key in props and props[key] is not None:
damage_bonus = safe_decimal(props[key])
break
# Range bonus
range_bonus = Decimal("0")
for key in ['Range', 'RangeBonus', 'RangeIncrease', 'range']:
if key in props and props[key] is not None:
range_bonus = safe_decimal(props[key])
break
# Decay - check in Economy dict or top-level
decay = Decimal("0")
if 'Economy' in props and isinstance(props['Economy'], dict):
decay = safe_decimal(props['Economy'].get('Decay'))
for key in ['Decay', 'decay', 'Cost']:
if key in props and props[key] is not None:
decay = safe_decimal(props[key])
break
# Efficiency
efficiency_bonus = Decimal("0")
if 'Economy' in props and isinstance(props['Economy'], dict):
efficiency_bonus = safe_decimal(props['Economy'].get('Efficiency'))
for key in ['Efficiency', 'efficiency', 'Eco', 'Eff']:
if key in props and props[key] is not None:
efficiency_bonus = safe_decimal(props[key])
break
# Get Economy data
economy = props.get('Economy', {}) or {}
decay = safe_decimal(economy.get('Decay'))
efficiency = safe_decimal(economy.get('Efficiency'))
# Determine attachment type from API type or name
api_type = props.get('Type', '').lower()
name = data.get('Name', '').lower()
if 'amplifier' in api_type or 'amp' in name:
if 'amplifier' in name or 'amp' in name:
attachment_type = 'amplifier'
elif 'scope' in api_type or 'scope' in name:
attachment_type = 'scope'
elif 'sight' in api_type or 'sight' in name:
attachment_type = 'sight'
elif 'absorber' in api_type or 'absorber' in name:
elif 'absorber' in name or 'extender' in name:
attachment_type = 'absorber'
else:
attachment_type = api_type or 'unknown'
# Parse based on attachment type
damage_bonus = Decimal("0")
range_bonus = Decimal("0")
zoom = 0
absorption = Decimal("0")
if attachment_type == 'amplifier':
# Amplifiers have damage in Properties.Damage
damage_data = props.get('Damage', {}) or {}
# Sum up all non-null damage values
for dmg_type, value in damage_data.items():
if value is not None:
damage_bonus += safe_decimal(value)
elif attachment_type == 'scope':
# Scopes have skill modification and zoom
skill_mod = safe_decimal(props.get('SkillModification'))
skill_bonus = safe_decimal(props.get('SkillBonus'))
range_bonus = skill_mod + skill_bonus
zoom = int(props.get('Zoom', 0) or 0)
elif attachment_type == 'absorber':
# Absorbers have absorption in Economy
absorption = safe_decimal(economy.get('Absorption'))
return cls(
id=data.get('Id', 0),
@ -219,7 +215,9 @@ class NexusAttachment(NexusItem):
damage_bonus=damage_bonus,
range_bonus=range_bonus,
decay=decay,
efficiency_bonus=efficiency_bonus,
efficiency_bonus=efficiency,
zoom=zoom,
absorption=absorption,
)

46
debug_attachments.py Normal file
View File

@ -0,0 +1,46 @@
"""
Debug script to check Entropia Nexus API attachment data
"""
import requests
import json
def fetch_and_debug(endpoint: str, name: str):
url = f"https://api.entropianexus.com/{endpoint}"
print(f"\n{'='*60}")
print(f"Fetching {name} from {endpoint}")
print(f"{'='*60}")
try:
resp = requests.get(url, timeout=30)
resp.raise_for_status()
data = resp.json()
if isinstance(data, list):
print(f"Got {len(data)} items")
if len(data) > 0:
# Show first item structure
first = data[0]
print(f"\nFirst item ({first.get('Name', 'Unknown')}):")
print(json.dumps(first, indent=2))
# Check a few more items for patterns
print(f"\nChecking first 5 items for Properties structure:")
for i, item in enumerate(data[:5]):
props = item.get('Properties', {})
print(f"\n{i+1}. {item.get('Name')}:")
print(f" Properties keys: {list(props.keys())}")
if 'Economy' in props:
print(f" Economy: {props['Economy']}")
else:
print(f"Unexpected response type: {type(data)}")
print(json.dumps(data, indent=2)[:1000])
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
# Check all attachment endpoints
fetch_and_debug("weaponamplifiers", "Weapon Amplifiers")
fetch_and_debug("weaponvisionattachments", "Weapon Vision Attachments (Scopes/Sights)")
fetch_and_debug("absorbers", "Absorbers")

View File

@ -136,12 +136,16 @@ class AttachmentSelectorDialog(QDialog):
self.preview_type = QLabel("-")
self.preview_damage = QLabel("-")
self.preview_range = QLabel("-")
self.preview_zoom = QLabel("-")
self.preview_absorption = QLabel("-")
self.preview_decay = QLabel("-")
self.preview_efficiency = QLabel("-")
preview_layout.addRow("Name:", self.preview_name)
preview_layout.addRow("Type:", self.preview_type)
preview_layout.addRow("Damage Bonus:", self.preview_damage)
preview_layout.addRow("Range Bonus:", self.preview_range)
preview_layout.addRow("Range/Acc Bonus:", self.preview_range)
preview_layout.addRow("Zoom:", self.preview_zoom)
preview_layout.addRow("Absorption:", self.preview_absorption)
preview_layout.addRow("Decay:", self.preview_decay)
preview_layout.addRow("Efficiency:", self.preview_efficiency)
layout.addWidget(self.preview_group)
@ -172,9 +176,9 @@ class AttachmentSelectorDialog(QDialog):
search_layout.addWidget(clear_btn)
layout.addLayout(search_layout)
# Tree
# Tree - different columns for different attachment types
tree = QTreeWidget()
tree.setHeaderLabels(["Name", "Type", "Dmg+", "Rng+", "Decay", "Efficiency"])
tree.setHeaderLabels(["Name", "Type", "Dmg+", "Rng/Acc", "Zoom", "Decay", "Eff%"])
header = tree.header()
header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
tree.itemSelectionChanged.connect(self._on_selection_changed)
@ -233,29 +237,45 @@ class AttachmentSelectorDialog(QDialog):
item.setText(0, att.name)
item.setText(1, att.attachment_type.title())
# Format damage with + sign if positive
dmg_text = f"+{att.damage_bonus}" if att.damage_bonus > 0 else str(att.damage_bonus)
# Column 2: Damage bonus (for amplifiers)
dmg_text = f"+{att.damage_bonus}" if att.damage_bonus > 0 else "-"
item.setText(2, dmg_text)
# Format range with + sign if positive
rng_text = f"+{att.range_bonus}" if att.range_bonus > 0 else str(att.range_bonus)
# Column 3: Range/Accuracy bonus (for scopes)
if att.attachment_type == 'scope' and att.range_bonus > 0:
rng_text = f"+{att.range_bonus:.1f}"
elif att.attachment_type == 'absorber' and att.absorption > 0:
rng_text = f"{att.absorption * 100:.0f}%"
else:
rng_text = "-"
item.setText(3, rng_text)
# Format decay (typically in PEC)
item.setText(4, f"{att.decay:.4f}")
# Column 4: Zoom (for scopes)
if att.attachment_type == 'scope' and att.zoom > 0:
zoom_text = f"{att.zoom}x"
else:
zoom_text = "-"
item.setText(4, zoom_text)
# Format efficiency as percentage
eff_text = f"{att.efficiency_bonus:.2f}%" if att.efficiency_bonus > 0 else "-"
item.setText(5, eff_text)
# Column 5: Decay (in PEC)
item.setText(5, f"{att.decay:.3f}")
# Column 6: Efficiency %
if att.efficiency_bonus > 0:
eff_text = f"{att.efficiency_bonus:.0f}%"
else:
eff_text = "-"
item.setText(6, eff_text)
# Color code by type
if att.attachment_type == "amplifier":
item.setForeground(0, QColor("#ff9800")) # Orange
# Highlight positive damage values
if att.damage_bonus > 0:
item.setForeground(2, QColor("#7FFF7F"))
elif att.attachment_type == "scope":
item.setForeground(0, QColor("#2196f3")) # Blue
if att.zoom > 0:
item.setForeground(4, QColor("#00FFFF"))
elif att.attachment_type == "sight":
item.setForeground(0, QColor("#4caf50")) # Green
elif att.attachment_type == "absorber":
@ -311,10 +331,47 @@ class AttachmentSelectorDialog(QDialog):
"""Update preview panel."""
self.preview_name.setText(attachment.name)
self.preview_type.setText(attachment.attachment_type.title())
self.preview_damage.setText(f"+{attachment.damage_bonus}")
self.preview_range.setText(f"+{attachment.range_bonus}")
self.preview_decay.setText(f"{attachment.decay:.2f} PEC")
self.preview_efficiency.setText(f"{attachment.efficiency_bonus:.1f}%")
# Damage bonus (amplifiers)
if attachment.damage_bonus > 0:
self.preview_damage.setText(f"+{attachment.damage_bonus}")
self.preview_damage.setStyleSheet("color: #7FFF7F;")
else:
self.preview_damage.setText("-")
self.preview_damage.setStyleSheet("")
# Range/Accuracy bonus (scopes)
if attachment.range_bonus > 0:
self.preview_range.setText(f"+{attachment.range_bonus:.1f}")
self.preview_range.setStyleSheet("color: #7FFF7F;")
else:
self.preview_range.setText("-")
self.preview_range.setStyleSheet("")
# Zoom (scopes)
if attachment.zoom > 0:
self.preview_zoom.setText(f"{attachment.zoom}x")
self.preview_zoom.setStyleSheet("color: #00FFFF;")
else:
self.preview_zoom.setText("-")
self.preview_zoom.setStyleSheet("")
# Absorption (absorbers)
if attachment.absorption > 0:
self.preview_absorption.setText(f"{attachment.absorption * 100:.1f}%")
self.preview_absorption.setStyleSheet("color: #9c27b0;")
else:
self.preview_absorption.setText("-")
self.preview_absorption.setStyleSheet("")
# Decay
self.preview_decay.setText(f"{attachment.decay:.4f} PEC")
# Efficiency
if attachment.efficiency_bonus > 0:
self.preview_efficiency.setText(f"{attachment.efficiency_bonus:.0f}%")
else:
self.preview_efficiency.setText("-")
def _clear_preview(self):
"""Clear preview panel."""
@ -322,8 +379,13 @@ class AttachmentSelectorDialog(QDialog):
self.preview_type.setText("-")
self.preview_damage.setText("-")
self.preview_range.setText("-")
self.preview_zoom.setText("-")
self.preview_absorption.setText("-")
self.preview_decay.setText("-")
self.preview_efficiency.setText("-")
# Reset stylesheets
for label in [self.preview_damage, self.preview_range, self.preview_zoom, self.preview_absorption]:
label.setStyleSheet("")
def _on_double_click(self, item, column):
"""Handle double click."""