From e8f0d7860e4962ee03c12aeafa412f9f33abdd1e Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Mon, 9 Feb 2026 17:12:44 +0000 Subject: [PATCH] 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 --- core/nexus_full_api.py | 74 +++++++++++++++--------------- debug_attachments.py | 46 +++++++++++++++++++ ui/attachment_selector.py | 96 ++++++++++++++++++++++++++++++++------- 3 files changed, 161 insertions(+), 55 deletions(-) create mode 100644 debug_attachments.py diff --git a/core/nexus_full_api.py b/core/nexus_full_api.py index 689f7f2..80eb0c0 100644 --- a/core/nexus_full_api.py +++ b/core/nexus_full_api.py @@ -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, ) diff --git a/debug_attachments.py b/debug_attachments.py new file mode 100644 index 0000000..f87b215 --- /dev/null +++ b/debug_attachments.py @@ -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") diff --git a/ui/attachment_selector.py b/ui/attachment_selector.py index cabcf4e..939f594 100644 --- a/ui/attachment_selector.py +++ b/ui/attachment_selector.py @@ -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."""