337 lines
13 KiB
Python
337 lines
13 KiB
Python
"""
|
|
Weapon Attachment Selector for Lemontropia Suite
|
|
Browse and search weapon attachments (scopes, sights, amplifiers, absorbers)
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
from PyQt6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
|
|
QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox,
|
|
QProgressBar, QGroupBox, QFormLayout, QComboBox, QTabWidget, QWidget
|
|
)
|
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
from PyQt6.QtGui import QColor
|
|
from typing import Optional, List
|
|
|
|
from core.nexus_full_api import get_nexus_api, NexusAttachment
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AttachmentLoaderThread(QThread):
|
|
"""Background thread for loading attachments from API."""
|
|
attachments_loaded = pyqtSignal(list)
|
|
error_occurred = pyqtSignal(str)
|
|
|
|
def run(self):
|
|
try:
|
|
api = get_nexus_api()
|
|
# Fetch all attachment types separately
|
|
all_attachments = []
|
|
|
|
# Get amplifiers
|
|
try:
|
|
amps = api.get_all_amplifiers()
|
|
for amp in amps:
|
|
amp.attachment_type = "amplifier"
|
|
all_attachments.extend(amps)
|
|
except Exception as e:
|
|
logger.warning(f"Could not load amplifiers: {e}")
|
|
|
|
# Get scopes/sights
|
|
try:
|
|
scopes = api.get_all_scopes()
|
|
for scope in scopes:
|
|
scope.attachment_type = "scope"
|
|
all_attachments.extend(scopes)
|
|
except Exception as e:
|
|
logger.warning(f"Could not load scopes: {e}")
|
|
|
|
# Get absorbers
|
|
try:
|
|
absorbers = api.get_all_absorbers()
|
|
for absorber in absorbers:
|
|
absorber.attachment_type = "absorber"
|
|
all_attachments.extend(absorbers)
|
|
except Exception as e:
|
|
logger.warning(f"Could not load absorbers: {e}")
|
|
|
|
self.attachments_loaded.emit(all_attachments)
|
|
except Exception as e:
|
|
self.error_occurred.emit(str(e))
|
|
|
|
|
|
class AttachmentSelectorDialog(QDialog):
|
|
"""Dialog for selecting weapon attachments from Entropia Nexus API."""
|
|
|
|
attachment_selected = pyqtSignal(NexusAttachment)
|
|
|
|
def __init__(self, parent=None, attachment_type: str = ""):
|
|
super().__init__(parent)
|
|
self.preferred_type = attachment_type.lower()
|
|
|
|
type_names = {
|
|
"amplifier": "Amplifier",
|
|
"scope": "Scope",
|
|
"sight": "Sight",
|
|
"absorber": "Absorber"
|
|
}
|
|
title_type = type_names.get(self.preferred_type, "Attachment")
|
|
|
|
self.setWindowTitle(f"Select {title_type} - Entropia Nexus")
|
|
self.setMinimumSize(900, 600)
|
|
|
|
self.all_attachments: List[NexusAttachment] = []
|
|
self.selected_attachment: Optional[NexusAttachment] = None
|
|
|
|
self._setup_ui()
|
|
self._load_data()
|
|
|
|
def _setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(10)
|
|
|
|
# Status
|
|
self.status_label = QLabel("Loading attachments from Entropia Nexus...")
|
|
layout.addWidget(self.status_label)
|
|
|
|
self.progress = QProgressBar()
|
|
self.progress.setRange(0, 0)
|
|
layout.addWidget(self.progress)
|
|
|
|
# Type filter tabs
|
|
self.tabs = QTabWidget()
|
|
|
|
# Create tabs for each attachment type
|
|
self.tab_all = self._create_attachment_tab("All Attachments")
|
|
self.tab_amps = self._create_attachment_tab("Amplifiers")
|
|
self.tab_scopes = self._create_attachment_tab("Scopes")
|
|
self.tab_sights = self._create_attachment_tab("Sights")
|
|
self.tab_absorbers = self._create_attachment_tab("Absorbers")
|
|
|
|
self.tabs.addTab(self.tab_all["widget"], "All")
|
|
self.tabs.addTab(self.tab_amps["widget"], "⚡ Amplifiers")
|
|
self.tabs.addTab(self.tab_scopes["widget"], "🔭 Scopes")
|
|
self.tabs.addTab(self.tab_sights["widget"], "🎯 Sights")
|
|
self.tabs.addTab(self.tab_absorbers["widget"], "🛡️ Absorbers")
|
|
|
|
# Set preferred tab
|
|
if self.preferred_type == "amplifier":
|
|
self.tabs.setCurrentIndex(1)
|
|
elif self.preferred_type == "scope":
|
|
self.tabs.setCurrentIndex(2)
|
|
elif self.preferred_type == "sight":
|
|
self.tabs.setCurrentIndex(3)
|
|
elif self.preferred_type == "absorber":
|
|
self.tabs.setCurrentIndex(4)
|
|
|
|
self.tabs.currentChanged.connect(self._on_tab_changed)
|
|
layout.addWidget(self.tabs)
|
|
|
|
# Preview panel
|
|
self.preview_group = QGroupBox("Attachment Preview")
|
|
preview_layout = QFormLayout(self.preview_group)
|
|
self.preview_name = QLabel("-")
|
|
self.preview_type = QLabel("-")
|
|
self.preview_damage = QLabel("-")
|
|
self.preview_range = 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("Decay:", self.preview_decay)
|
|
preview_layout.addRow("Efficiency:", self.preview_efficiency)
|
|
layout.addWidget(self.preview_group)
|
|
|
|
# Buttons
|
|
buttons = QDialogButtonBox(
|
|
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
|
)
|
|
buttons.accepted.connect(self._on_accept)
|
|
buttons.rejected.connect(self.reject)
|
|
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
|
|
self.ok_button.setEnabled(False)
|
|
layout.addWidget(buttons)
|
|
|
|
def _create_attachment_tab(self, title: str) -> dict:
|
|
"""Create a tab with search and tree for attachments."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
# Search
|
|
search_layout = QHBoxLayout()
|
|
search_layout.addWidget(QLabel("Search:"))
|
|
search_input = QLineEdit()
|
|
search_input.setPlaceholderText(f"Search {title.lower()}...")
|
|
search_layout.addWidget(search_input)
|
|
|
|
clear_btn = QPushButton("Clear")
|
|
search_layout.addWidget(clear_btn)
|
|
layout.addLayout(search_layout)
|
|
|
|
# Tree
|
|
tree = QTreeWidget()
|
|
tree.setHeaderLabels(["Name", "Type", "Dmg+", "Rng+", "Decay", "Efficiency"])
|
|
header = tree.header()
|
|
header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
|
tree.itemSelectionChanged.connect(self._on_selection_changed)
|
|
tree.itemDoubleClicked.connect(self._on_double_click)
|
|
layout.addWidget(tree)
|
|
|
|
return {
|
|
"widget": widget,
|
|
"search": search_input,
|
|
"clear": clear_btn,
|
|
"tree": tree
|
|
}
|
|
|
|
def _load_data(self):
|
|
"""Load attachments in background thread."""
|
|
self.loader = AttachmentLoaderThread()
|
|
self.loader.attachments_loaded.connect(self._on_attachments_loaded)
|
|
self.loader.error_occurred.connect(self._on_load_error)
|
|
self.loader.start()
|
|
|
|
def _on_attachments_loaded(self, attachments: List[NexusAttachment]):
|
|
"""Handle loaded attachments."""
|
|
self.all_attachments = attachments
|
|
self.status_label.setText(f"Loaded {len(attachments)} attachments from Entropia Nexus")
|
|
self.progress.setRange(0, 100)
|
|
self.progress.setValue(100)
|
|
|
|
# Populate all tabs
|
|
self._populate_tab(self.tab_all, self.all_attachments)
|
|
self._populate_tab(self.tab_amps, [a for a in attachments if a.attachment_type == "amplifier"])
|
|
self._populate_tab(self.tab_scopes, [a for a in attachments if a.attachment_type == "scope"])
|
|
self._populate_tab(self.tab_sights, [a for a in attachments if a.attachment_type == "sight"])
|
|
self._populate_tab(self.tab_absorbers, [a for a in attachments if a.attachment_type == "absorber"])
|
|
|
|
# Connect search signals
|
|
for tab in [self.tab_all, self.tab_amps, self.tab_scopes, self.tab_sights, self.tab_absorbers]:
|
|
tab["search"].textChanged.connect(lambda text, t=tab: self._on_search_changed(t, text))
|
|
tab["clear"].clicked.connect(lambda t=tab: t["search"].clear())
|
|
|
|
def _on_load_error(self, error: str):
|
|
"""Handle load error."""
|
|
self.status_label.setText(f"Error loading attachments: {error}")
|
|
self.progress.setRange(0, 100)
|
|
self.progress.setValue(0)
|
|
|
|
def _populate_tab(self, tab: dict, attachments: List[NexusAttachment]):
|
|
"""Populate a tab's tree with attachments."""
|
|
tree = tab["tree"]
|
|
tree.clear()
|
|
|
|
# Sort by damage bonus (amplifiers) or range (scopes)
|
|
attachments = sorted(attachments, key=lambda a: a.damage_bonus + a.range_bonus, reverse=True)
|
|
|
|
for att in attachments:
|
|
item = QTreeWidgetItem()
|
|
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)
|
|
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)
|
|
item.setText(3, rng_text)
|
|
|
|
# Format decay (typically in PEC)
|
|
item.setText(4, f"{att.decay:.4f}")
|
|
|
|
# Format efficiency as percentage
|
|
eff_text = f"{att.efficiency_bonus:.2f}%" if att.efficiency_bonus > 0 else "-"
|
|
item.setText(5, 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
|
|
elif att.attachment_type == "sight":
|
|
item.setForeground(0, QColor("#4caf50")) # Green
|
|
elif att.attachment_type == "absorber":
|
|
item.setForeground(0, QColor("#9c27b0")) # Purple
|
|
|
|
item.setData(0, Qt.ItemDataRole.UserRole, att)
|
|
tree.addTopLevelItem(item)
|
|
|
|
item.setData(0, Qt.ItemDataRole.UserRole, att)
|
|
tree.addTopLevelItem(item)
|
|
|
|
def _on_search_changed(self, tab: dict, text: str):
|
|
"""Handle search text change in a tab."""
|
|
tree = tab["tree"]
|
|
for i in range(tree.topLevelItemCount()):
|
|
item = tree.topLevelItem(i)
|
|
attachment = item.data(0, Qt.ItemDataRole.UserRole)
|
|
if text.lower() in attachment.name.lower():
|
|
item.setHidden(False)
|
|
else:
|
|
item.setHidden(True)
|
|
|
|
def _on_tab_changed(self, index: int):
|
|
"""Handle tab change."""
|
|
# Clear selection when changing tabs
|
|
self.selected_attachment = None
|
|
self.ok_button.setEnabled(False)
|
|
self._clear_preview()
|
|
|
|
def _on_selection_changed(self):
|
|
"""Handle selection change in current tab."""
|
|
current_tab = self._get_current_tab()
|
|
if current_tab:
|
|
items = current_tab["tree"].selectedItems()
|
|
if items:
|
|
self.selected_attachment = items[0].data(0, Qt.ItemDataRole.UserRole)
|
|
self.ok_button.setEnabled(True)
|
|
self._update_preview(self.selected_attachment)
|
|
else:
|
|
self.selected_attachment = None
|
|
self.ok_button.setEnabled(False)
|
|
self._clear_preview()
|
|
|
|
def _get_current_tab(self) -> Optional[dict]:
|
|
"""Get the currently active tab data."""
|
|
index = self.tabs.currentIndex()
|
|
tabs = [self.tab_all, self.tab_amps, self.tab_scopes, self.tab_sights, self.tab_absorbers]
|
|
if 0 <= index < len(tabs):
|
|
return tabs[index]
|
|
return None
|
|
|
|
def _update_preview(self, attachment: NexusAttachment):
|
|
"""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}%")
|
|
|
|
def _clear_preview(self):
|
|
"""Clear preview panel."""
|
|
self.preview_name.setText("-")
|
|
self.preview_type.setText("-")
|
|
self.preview_damage.setText("-")
|
|
self.preview_range.setText("-")
|
|
self.preview_decay.setText("-")
|
|
self.preview_efficiency.setText("-")
|
|
|
|
def _on_double_click(self, item, column):
|
|
"""Handle double click."""
|
|
self._on_accept()
|
|
|
|
def _on_accept(self):
|
|
"""Handle OK button."""
|
|
if self.selected_attachment:
|
|
self.attachment_selected.emit(self.selected_attachment)
|
|
self.accept()
|