""" Searchable Armor Selector for Lemontropia Suite Browse and search armors from Entropia Nexus API """ from decimal import Decimal from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, QProgressBar, QGroupBox, QFormLayout, QTabWidget, QWidget ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from typing import Optional, List from core.nexus_full_api import get_nexus_api, NexusArmor, NexusArmorSet class ArmorLoaderThread(QThread): """Background thread for loading armors from API.""" armors_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = get_nexus_api() armors = api.get_all_armors() self.armors_loaded.emit(armors) except Exception as e: self.error_occurred.emit(str(e)) class ArmorSetLoaderThread(QThread): """Background thread for loading armor sets from API.""" armor_sets_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = get_nexus_api() armor_sets = api.get_all_armor_sets() self.armor_sets_loaded.emit(armor_sets) except Exception as e: self.error_occurred.emit(str(e)) class ArmorSelectorDialog(QDialog): """Dialog for selecting armors from Entropia Nexus API with search and sets.""" armor_selected = pyqtSignal(NexusArmor) armor_set_selected = pyqtSignal(NexusArmorSet) # New signal for full sets def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Select Armor - Entropia Nexus") self.setMinimumSize(1000, 700) self.all_armors: List[NexusArmor] = [] self.all_armor_sets: List[NexusArmorSet] = [] self.selected_armor: Optional[NexusArmor] = None self.selected_armor_set: Optional[NexusArmorSet] = None self._setup_ui() self._load_data() def _setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # Status self.status_label = QLabel("Loading armors from Entropia Nexus...") layout.addWidget(self.status_label) self.progress = QProgressBar() self.progress.setRange(0, 0) # Indeterminate layout.addWidget(self.progress) # Tab widget for Individual Pieces vs Full Sets self.tabs = QTabWidget() # === INDIVIDUAL PIECES TAB === self.tab_individual = QWidget() individual_layout = QVBoxLayout(self.tab_individual) # Search search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) self.search_input = QLineEdit() self.search_input.setPlaceholderText("Type to search individual armor pieces...") self.search_input.textChanged.connect(self._on_search) search_layout.addWidget(self.search_input) self.clear_btn = QPushButton("Clear") self.clear_btn.clicked.connect(self._clear_search) search_layout.addWidget(self.clear_btn) individual_layout.addLayout(search_layout) # Results tree for individual pieces self.results_tree = QTreeWidget() self.results_tree.setHeaderLabels([ "Name", "Type", "Durability", "Impact", "Cut", "Stab", "Burn", "Cold", "Acid", "Electric", "Total Prot" ]) header = self.results_tree.header() header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) header.setStretchLastSection(False) self.results_tree.itemSelectionChanged.connect(self._on_selection_changed) self.results_tree.itemDoubleClicked.connect(self._on_double_click) individual_layout.addWidget(self.results_tree) self.tabs.addTab(self.tab_individual, "🛡️ Individual Pieces") # === FULL SETS TAB === self.tab_sets = QWidget() sets_layout = QVBoxLayout(self.tab_sets) # Search for sets set_search_layout = QHBoxLayout() set_search_layout.addWidget(QLabel("Search:")) self.set_search_input = QLineEdit() self.set_search_input.setPlaceholderText("Type to search armor sets (e.g., 'Ghost', 'Shogun')...") self.set_search_input.textChanged.connect(self._on_set_search) set_search_layout.addWidget(self.set_search_input) self.set_clear_btn = QPushButton("Clear") self.set_clear_btn.clicked.connect(self._clear_set_search) set_search_layout.addWidget(self.set_clear_btn) sets_layout.addLayout(set_search_layout) # Results tree for sets self.sets_tree = QTreeWidget() self.sets_tree.setHeaderLabels([ "Set Name", "Pieces Count", "Total Impact", "Total Cut", "Total Stab", "Total Burn", "Total Cold", "Total Acid", "Total Electric", "Total Prot" ]) sets_header = self.sets_tree.header() sets_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) sets_header.setStretchLastSection(False) self.sets_tree.itemSelectionChanged.connect(self._on_set_selection_changed) self.sets_tree.itemDoubleClicked.connect(self._on_set_double_click) sets_layout.addWidget(self.sets_tree) self.tabs.addTab(self.tab_sets, "⚔️ Full Armor Sets") layout.addWidget(self.tabs) # Preview panel (shared) self.preview_group = QGroupBox("Armor Preview") preview_layout = QFormLayout(self.preview_group) self.preview_name = QLabel("-") self.preview_type = QLabel("-") self.preview_durability = QLabel("-") self.preview_protection = QLabel("-") preview_layout.addRow("Name:", self.preview_name) preview_layout.addRow("Type:", self.preview_type) preview_layout.addRow("Durability:", self.preview_durability) preview_layout.addRow("Protection:", self.preview_protection) 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) # Update preview when tab changes self.tabs.currentChanged.connect(self._on_tab_changed) def _load_data(self): """Load armors and armor sets in background threads.""" # Load individual armors self.loader = ArmorLoaderThread() self.loader.armors_loaded.connect(self._on_armors_loaded) self.loader.error_occurred.connect(self._on_load_error) self.loader.start() # Load armor sets self.set_loader = ArmorSetLoaderThread() self.set_loader.armor_sets_loaded.connect(self._on_armor_sets_loaded) self.set_loader.error_occurred.connect(self._on_set_load_error) self.set_loader.start() def _on_armors_loaded(self, armors: List[NexusArmor]): """Handle loaded armors.""" self.all_armors = armors self.status_label.setText(f"Loaded {len(armors)} armors and {len(self.all_armor_sets)} armor sets from Entropia Nexus") self.progress.setRange(0, 100) self.progress.setValue(100) self._populate_results(armors) def _on_load_error(self, error: str): """Handle load error.""" self.status_label.setText(f"Error loading armors: {error}") self.progress.setRange(0, 100) self.progress.setValue(0) def _on_armor_sets_loaded(self, armor_sets: List[NexusArmorSet]): """Handle loaded armor sets.""" self.all_armor_sets = armor_sets self._populate_sets(armor_sets) def _on_set_load_error(self, error: str): """Handle armor set load error.""" self.status_label.setText(f"Error loading armor sets: {error}") def _populate_results(self, armors: List[NexusArmor]): """Populate results tree with individual armor pieces.""" self.results_tree.clear() for armor in armors: item = QTreeWidgetItem() item.setText(0, armor.name) item.setText(1, armor.type) item.setText(2, str(armor.durability)) item.setText(3, str(armor.protection_impact)) item.setText(4, str(armor.protection_cut)) item.setText(5, str(armor.protection_stab)) item.setText(6, str(armor.protection_burn)) item.setText(7, str(armor.protection_cold)) item.setText(8, str(armor.protection_acid)) item.setText(9, str(armor.protection_electric)) total = armor.protection_impact + armor.protection_cut + armor.protection_stab item.setText(10, str(total)) item.setData(0, Qt.ItemDataRole.UserRole, armor) self.results_tree.addTopLevelItem(item) def _populate_sets(self, armor_sets: List[NexusArmorSet]): """Populate sets tree with full armor sets.""" self.sets_tree.clear() for armor_set in armor_sets: item = QTreeWidgetItem() item.setText(0, armor_set.name) item.setText(1, str(len(armor_set.pieces))) # Get total protection from the set prot = armor_set.total_protection item.setText(2, str(prot.impact)) item.setText(3, str(prot.cut)) item.setText(4, str(prot.stab)) item.setText(5, str(prot.burn)) item.setText(6, str(prot.cold)) item.setText(7, str(prot.acid)) item.setText(8, str(prot.electric)) total = prot.get_total() item.setText(9, str(total)) item.setData(0, Qt.ItemDataRole.UserRole, armor_set) self.sets_tree.addTopLevelItem(item) def _on_search(self, text: str): """Handle search text change for individual pieces.""" if not text: self._populate_results(self.all_armors) return query = text.lower() filtered = [a for a in self.all_armors if query in a.name.lower()] self._populate_results(filtered) self.status_label.setText(f"Found {len(filtered)} armors matching '{text}'") def _clear_search(self): """Clear search for individual pieces.""" self.search_input.clear() self._populate_results(self.all_armors) self.status_label.setText(f"Showing all {len(self.all_armors)} armors") def _on_set_search(self, text: str): """Handle search text change for armor sets.""" if not text: self._populate_sets(self.all_armor_sets) return query = text.lower() filtered = [s for s in self.all_armor_sets if query in s.name.lower()] self._populate_sets(filtered) self.status_label.setText(f"Found {len(filtered)} armor sets matching '{text}'") def _clear_set_search(self): """Clear search for armor sets.""" self.set_search_input.clear() self._populate_sets(self.all_armor_sets) self.status_label.setText(f"Showing all {len(self.all_armor_sets)} armor sets") def _on_tab_changed(self, index: int): """Handle tab change - clear selection and update UI.""" self.selected_armor = None self.selected_armor_set = None self.ok_button.setEnabled(False) self._clear_preview() def _clear_preview(self): """Clear preview panel.""" self.preview_name.setText("-") self.preview_type.setText("-") self.preview_durability.setText("-") self.preview_protection.setText("-") def _on_selection_changed(self): """Handle selection change for individual pieces.""" items = self.results_tree.selectedItems() if items: self.selected_armor = items[0].data(0, Qt.ItemDataRole.UserRole) self.selected_armor_set = None self.ok_button.setEnabled(True) self._update_preview(self.selected_armor) else: self.selected_armor = None self.ok_button.setEnabled(False) def _on_set_selection_changed(self): """Handle selection change for armor sets.""" items = self.sets_tree.selectedItems() if items: self.selected_armor_set = items[0].data(0, Qt.ItemDataRole.UserRole) self.selected_armor = None self.ok_button.setEnabled(True) self._update_set_preview(self.selected_armor_set) else: self.selected_armor_set = None self.ok_button.setEnabled(False) def _update_preview(self, armor: NexusArmor): """Update preview panel for individual armor.""" self.preview_name.setText(armor.name) self.preview_type.setText(f"Piece: {armor.type}") self.preview_durability.setText(f"{armor.durability} (~{Decimal('20') / (Decimal('1') - Decimal(armor.durability) / Decimal('100000')):.2f} hp/pec)") prot_parts = [] if armor.protection_impact > 0: prot_parts.append(f"Imp:{armor.protection_impact}") if armor.protection_cut > 0: prot_parts.append(f"Cut:{armor.protection_cut}") if armor.protection_stab > 0: prot_parts.append(f"Stab:{armor.protection_stab}") if armor.protection_burn > 0: prot_parts.append(f"Burn:{armor.protection_burn}") if armor.protection_cold > 0: prot_parts.append(f"Cold:{armor.protection_cold}") if armor.protection_acid > 0: prot_parts.append(f"Acid:{armor.protection_acid}") if armor.protection_electric > 0: prot_parts.append(f"Elec:{armor.protection_electric}") self.preview_protection.setText(", ".join(prot_parts) if prot_parts else "None") def _update_set_preview(self, armor_set: NexusArmorSet): """Update preview panel for armor set.""" self.preview_name.setText(armor_set.name) self.preview_type.setText(f"Full Set ({len(armor_set.pieces)} pieces)") # Show pieces in the set pieces_text = "\n".join([f" • {piece}" for piece in armor_set.pieces[:7]]) if len(armor_set.pieces) > 7: pieces_text += f"\n ... and {len(armor_set.pieces) - 7} more" self.preview_durability.setText(f"Pieces:\n{pieces_text}") # Show total protection prot = armor_set.total_protection prot_parts = [] if prot.impact > 0: prot_parts.append(f"Imp:{prot.impact}") if prot.cut > 0: prot_parts.append(f"Cut:{prot.cut}") if prot.stab > 0: prot_parts.append(f"Stab:{prot.stab}") if prot.burn > 0: prot_parts.append(f"Burn:{prot.burn}") if prot.cold > 0: prot_parts.append(f"Cold:{prot.cold}") if prot.acid > 0: prot_parts.append(f"Acid:{prot.acid}") if prot.electric > 0: prot_parts.append(f"Elec:{prot.electric}") total_prot = f"Total: {prot.get_total()}\n" + ", ".join(prot_parts) if armor_set.set_bonus: total_prot += f"\n\n✨ Set Bonus: {armor_set.set_bonus}" self.preview_protection.setText(total_prot) def _on_double_click(self, item, column): """Handle double click on individual armor.""" if self.selected_armor: self._on_accept() def _on_set_double_click(self, item, column): """Handle double click on armor set.""" if self.selected_armor_set: self._on_accept() def _on_accept(self): """Handle OK button.""" if self.tabs.currentIndex() == 0 and self.selected_armor: # Individual piece tab self.armor_selected.emit(self.selected_armor) self.accept() elif self.tabs.currentIndex() == 1 and self.selected_armor_set: # Full sets tab self.armor_set_selected.emit(self.selected_armor_set) self.accept() """Handle OK button.""" if self.selected_armor: self.armor_selected.emit(self.selected_armor) self.accept()