""" Gear Selector Dialog for Lemontropia Suite Uses Entropia Nexus API to search and select real gear. """ from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QTabWidget, QGroupBox, QFormLayout, QMessageBox, QHeaderView ) from PyQt6.QtCore import Qt, pyqtSignal, QThread from decimal import Decimal from typing import Optional, List from core.nexus_api import EntropiaNexusAPI, WeaponStats, ArmorStats, FinderStats class WeaponLoaderThread(QThread): """Thread to load weapons from API without blocking UI.""" weapons_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = EntropiaNexusAPI() weapons = api.get_all_weapons() self.weapons_loaded.emit(weapons) except Exception as e: self.error_occurred.emit(str(e)) class ArmorLoaderThread(QThread): """Thread to load armors from API.""" armors_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = EntropiaNexusAPI() armors = api.get_all_armors() self.armors_loaded.emit(armors) except Exception as e: self.error_occurred.emit(str(e)) class FinderLoaderThread(QThread): """Thread to load finders from API.""" finders_loaded = pyqtSignal(list) error_occurred = pyqtSignal(str) def run(self): try: api = EntropiaNexusAPI() finders = api.get_all_finders() self.finders_loaded.emit(finders) except Exception as e: self.error_occurred.emit(str(e)) class GearSelectorDialog(QDialog): """Dialog for selecting gear from Entropia Nexus.""" gear_selected = pyqtSignal(str, str, dict) # type, name, stats def __init__(self, gear_type: str = "weapon", parent=None): super().__init__(parent) self.gear_type = gear_type self.selected_gear = None self.items: List = [] self.api = EntropiaNexusAPI() self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus") self.setMinimumSize(800, 600) self.setup_ui() self.load_data_async() def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # Status label self.status_label = QLabel(f"Loading {self.gear_type}s from Entropia Nexus...") layout.addWidget(self.status_label) # Search box search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) self.search_input = QLineEdit() self.search_input.setPlaceholderText(f"Search {self.gear_type}s...") self.search_input.returnPressed.connect(self.on_search) self.search_input.setEnabled(False) search_layout.addWidget(self.search_input) self.search_btn = QPushButton("Search") self.search_btn.clicked.connect(self.on_search) self.search_btn.setEnabled(False) search_layout.addWidget(self.search_btn) layout.addLayout(search_layout) # Results list self.results_tree = QTreeWidget() self.setup_columns() self.results_tree.setAlternatingRowColors(True) self.results_tree.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection) self.results_tree.itemSelectionChanged.connect(self.on_selection_changed) self.results_tree.itemDoubleClicked.connect(self.on_item_double_clicked) layout.addWidget(self.results_tree) # Stats preview self.stats_group = QGroupBox("Stats Preview") self.stats_layout = QFormLayout(self.stats_group) self.stats_layout.addRow("Select an item to view stats", QLabel("")) layout.addWidget(self.stats_group) # Buttons button_box = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) button_box.accepted.connect(self.on_accept) button_box.rejected.connect(self.reject) self.ok_btn = button_box.button(QDialogButtonBox.StandardButton.Ok) self.ok_btn.setEnabled(False) self.ok_btn.setText(f"Select {self.gear_type.title()}") layout.addWidget(button_box) def setup_columns(self): """Setup tree columns based on gear type.""" if self.gear_type == "weapon": self.results_tree.setHeaderLabels(["Name", "Type", "Damage", "DPP", "Cost/h", "Eff%"]) header = self.results_tree.header() header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(4, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(5, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 100) header.resizeSection(2, 60) header.resizeSection(3, 60) header.resizeSection(4, 70) header.resizeSection(5, 50) elif self.gear_type == "armor": self.results_tree.setHeaderLabels(["Name", "Protection", "Durability"]) header = self.results_tree.header() header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 100) header.resizeSection(2, 100) elif self.gear_type == "finder": self.results_tree.setHeaderLabels(["Name", "Type", "Depth", "Radius", "Decay"]) header = self.results_tree.header() header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) for i in range(1, 5): header.setSectionResizeMode(i, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 100) header.resizeSection(2, 80) header.resizeSection(3, 80) header.resizeSection(4, 80) def load_data_async(self): """Load data in background thread.""" if self.gear_type == "weapon": self.loader_thread = WeaponLoaderThread() self.loader_thread.weapons_loaded.connect(self.on_data_loaded) self.loader_thread.error_occurred.connect(self.on_load_error) elif self.gear_type == "armor": self.loader_thread = ArmorLoaderThread() self.loader_thread.armors_loaded.connect(self.on_data_loaded) self.loader_thread.error_occurred.connect(self.on_load_error) elif self.gear_type == "finder": self.loader_thread = FinderLoaderThread() self.loader_thread.finders_loaded.connect(self.on_data_loaded) self.loader_thread.error_occurred.connect(self.on_load_error) else: return self.loader_thread.start() def on_data_loaded(self, items): """Handle loaded data.""" self.items = items self.status_label.setText(f"Loaded {len(items)} {self.gear_type}s from Entropia Nexus") self.search_input.setEnabled(True) self.search_btn.setEnabled(True) self.populate_tree(items[:100]) def on_load_error(self, error): """Handle load error.""" self.status_label.setText(f"Error: {error}") def populate_tree(self, items): """Populate tree with items.""" self.results_tree.clear() if self.gear_type == "weapon": for w in items: item = QTreeWidgetItem([ w.name, f"{w.type} {w.category}", str(w.total_damage), f"{w.dpp:.2f}", f"{w.cost_per_hour:.0f}", f"{w.efficiency:.0f}" if w.efficiency else "-" ]) item.setData(0, Qt.ItemDataRole.UserRole, w) self.results_tree.addTopLevelItem(item) elif self.gear_type == "armor": for a in items: item = QTreeWidgetItem([ a.name, str(a.total_protection), str(a.durability) ]) item.setData(0, Qt.ItemDataRole.UserRole, a) self.results_tree.addTopLevelItem(item) elif self.gear_type == "finder": for f in items: item = QTreeWidgetItem([ f.name, f.type, str(f.depth), str(f.radius), str(f.decay) ]) item.setData(0, Qt.ItemDataRole.UserRole, f) self.results_tree.addTopLevelItem(item) def on_search(self): """Search items.""" query = self.search_input.text().strip().lower() if not query: self.populate_tree(self.items[:100]) return if self.gear_type == "weapon": results = self.api.search_weapons(query) else: results = [i for i in self.items if query in i.name.lower()] self.populate_tree(results) self.status_label.setText(f"Found {len(results)} {self.gear_type}s matching '{query}'") def on_selection_changed(self): """Handle selection change.""" selected = self.results_tree.selectedItems() if selected: item = selected[0].data(0, Qt.ItemDataRole.UserRole) self.selected_gear = item self.ok_btn.setEnabled(True) self.update_stats_preview(item) else: self.selected_gear = None self.ok_btn.setEnabled(False) def update_stats_preview(self, item): """Update stats preview.""" while self.stats_layout.rowCount() > 0: self.stats_layout.removeRow(0) if self.gear_type == "weapon" and isinstance(item, WeaponStats): self.stats_layout.addRow("Name:", QLabel(item.name)) self.stats_layout.addRow("Type:", QLabel(f"{item.type} {item.category}")) self.stats_layout.addRow("Damage:", QLabel(str(item.total_damage))) self.stats_layout.addRow("DPP:", QLabel(f"{item.dpp:.3f}")) self.stats_layout.addRow("Decay:", QLabel(f"{item.decay} PEC" if item.decay else "-")) self.stats_layout.addRow("Ammo:", QLabel(f"{item.ammo_burn} per shot" if item.ammo_burn else "-")) self.stats_layout.addRow("Cost/hour:", QLabel(f"{item.cost_per_hour:.2f} PED")) elif self.gear_type == "armor" and isinstance(item, ArmorStats): self.stats_layout.addRow("Name:", QLabel(item.name)) self.stats_layout.addRow("Total Protection:", QLabel(str(item.total_protection))) self.stats_layout.addRow("Durability:", QLabel(str(item.durability))) elif self.gear_type == "finder" and isinstance(item, FinderStats): self.stats_layout.addRow("Name:", QLabel(item.name)) self.stats_layout.addRow("Depth:", QLabel(f"{item.depth}m")) self.stats_layout.addRow("Radius:", QLabel(f"{item.radius}m")) self.stats_layout.addRow("Decay:", QLabel(f"{item.decay} PEC")) def on_item_double_clicked(self, item, column): """Handle double click.""" self.on_accept() def on_accept(self): """Handle OK button.""" if self.selected_gear: if self.gear_type == "weapon": w = self.selected_gear self.gear_selected.emit("weapon", w.name, { 'id': w.id, 'item_id': w.item_id, 'dpp': float(w.dpp), 'cost_per_hour': float(w.cost_per_hour), 'total_damage': float(w.total_damage), }) elif self.gear_type == "armor": a = self.selected_gear self.gear_selected.emit("armor", a.name, { 'id': a.id, 'item_id': a.item_id, 'protection': float(a.total_protection), }) elif self.gear_type == "finder": f = self.selected_gear self.gear_selected.emit("finder", f.name, { 'id': f.id, 'item_id': f.item_id, 'depth': float(f.depth), 'radius': float(f.radius), }) self.accept() if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) dialog = GearSelectorDialog("weapon") if dialog.exec() == QDialog.DialogCode.Accepted: print("Selected:", dialog.selected_gear.name if dialog.selected_gear else None) sys.exit(0) self.api = EntropiaNexusAPI() self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus") self.setMinimumSize(800, 600) self.setup_ui() # Load weapons in background self.load_weapons_async() def setup_ui(self): layout = QVBoxLayout(self) layout.setSpacing(10) # Status label self.status_label = QLabel("Loading weapons from Entropia Nexus...") layout.addWidget(self.status_label) # Search box search_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) self.search_input = QLineEdit() self.search_input.setPlaceholderText("Search weapons (e.g., 'ArMatrix', 'Opalo')...") self.search_input.returnPressed.connect(self.on_search) self.search_input.setEnabled(False) search_layout.addWidget(self.search_input) self.search_btn = QPushButton("Search") self.search_btn.clicked.connect(self.on_search) self.search_btn.setEnabled(False) search_layout.addWidget(self.search_btn) layout.addLayout(search_layout) # Results list self.results_tree = QTreeWidget() self.results_tree.setHeaderLabels(["Name", "Type", "Damage", "DPP", "Cost/h", "Efficiency"]) self.results_tree.setAlternatingRowColors(True) self.results_tree.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection) self.results_tree.itemSelectionChanged.connect(self.on_selection_changed) self.results_tree.itemDoubleClicked.connect(self.on_item_double_clicked) # Adjust columns header = self.results_tree.header() header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(4, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(5, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 100) header.resizeSection(2, 70) header.resizeSection(3, 60) header.resizeSection(4, 70) header.resizeSection(5, 70) layout.addWidget(self.results_tree) # Stats preview self.stats_group = QGroupBox("Stats Preview") self.stats_layout = QFormLayout(self.stats_group) self.stats_layout.addRow("Select a weapon to view detailed stats", QLabel("")) layout.addWidget(self.stats_group) # Buttons button_box = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) button_box.accepted.connect(self.on_accept) button_box.rejected.connect(self.reject) self.ok_btn = button_box.button(QDialogButtonBox.StandardButton.Ok) self.ok_btn.setEnabled(False) self.ok_btn.setText("Select Weapon") layout.addWidget(button_box) def load_weapons_async(self): """Load weapons in background thread.""" self.loader_thread = WeaponLoaderThread(self.api) self.loader_thread.weapons_loaded.connect(self.on_weapons_loaded) self.loader_thread.error_occurred.connect(self.on_load_error) self.loader_thread.start() def on_weapons_loaded(self, weapons: List[WeaponStats]): """Handle loaded weapons.""" self.weapons = weapons self.status_label.setText(f"Loaded {len(weapons)} weapons from Entropia Nexus") self.search_input.setEnabled(True) self.search_btn.setEnabled(True) self.populate_tree(weapons[:100]) # Show first 100 initially def on_load_error(self, error: str): """Handle load error.""" self.status_label.setText(f"Error loading weapons: {error}") def populate_tree(self, weapons: List[WeaponStats]): """Populate tree with weapons.""" self.results_tree.clear() for w in weapons: item = QTreeWidgetItem([ w.name, f"{w.type} {w.category}", str(w.total_damage), f"{w.dpp:.2f}", f"{w.cost_per_hour:.0f}", f"{w.efficiency:.1f}" if w.efficiency else "-" ]) item.setData(0, Qt.ItemDataRole.UserRole, w) self.results_tree.addTopLevelItem(item) def on_search(self): """Search weapons.""" query = self.search_input.text().strip().lower() if not query: self.populate_tree(self.weapons[:100]) return import asyncio loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) results = loop.run_until_complete(self.api.search_weapons(query)) loop.close() self.populate_tree(results) self.status_label.setText(f"Found {len(results)} weapons matching '{query}'") def on_selection_changed(self): """Handle selection change.""" selected = self.results_tree.selectedItems() if selected: weapon = selected[0].data(0, Qt.ItemDataRole.UserRole) self.selected_gear = weapon self.ok_btn.setEnabled(True) self.update_stats_preview(weapon) else: self.selected_gear = None self.ok_btn.setEnabled(False) def update_stats_preview(self, w: WeaponStats): """Update stats preview.""" # Clear previous while self.stats_layout.rowCount() > 0: self.stats_layout.removeRow(0) self.stats_layout.addRow("Name:", QLabel(w.name)) self.stats_layout.addRow("Type:", QLabel(f"{w.type} {w.category}")) self.stats_layout.addRow("Class:", QLabel(w.weapon_class)) self.stats_layout.addRow("Damage:", QLabel(str(w.total_damage))) self.stats_layout.addRow("DPP:", QLabel(f"{w.dpp:.3f}")) self.stats_layout.addRow("Decay:", QLabel(f"{w.decay} PEC" if w.decay else "-")) self.stats_layout.addRow("Ammo:", QLabel(f"{w.ammo_burn} per shot" if w.ammo_burn else "-")) self.stats_layout.addRow("Uses/min:", QLabel(str(w.uses_per_minute))) self.stats_layout.addRow("Range:", QLabel(f"{w.range_meters}m" if w.range_meters else "-")) self.stats_layout.addRow("Efficiency:", QLabel(f"{w.efficiency}%" if w.efficiency else "-")) self.stats_layout.addRow("Cost/hour:", QLabel(f"{w.cost_per_hour:.2f} PED")) self.stats_layout.addRow("Max TT:", QLabel(f"{w.max_tt} PED")) def on_item_double_clicked(self, item, column): """Handle double click.""" self.on_accept() def on_accept(self): """Handle OK button.""" if self.selected_gear: w = self.selected_gear self.gear_selected.emit( "weapon", w.name, { 'id': w.id, 'item_id': w.item_id, 'dpp': float(w.dpp), 'cost_per_hour': float(w.cost_per_hour), 'total_damage': float(w.total_damage), } ) self.accept() if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) dialog = GearSelectorDialog("weapon") if dialog.exec() == QDialog.DialogCode.Accepted: print("Selected:", dialog.selected_gear.name if dialog.selected_gear else None) sys.exit(0) self.results_tree.itemDoubleClicked.connect(self.on_item_double_clicked) # Adjust columns header = self.results_tree.header() header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed) header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed) header.resizeSection(1, 120) header.resizeSection(2, 100) header.resizeSection(3, 100) layout.addWidget(self.results_tree) # Stats preview self.stats_group = QGroupBox("Stats Preview") self.stats_layout = QFormLayout(self.stats_group) self.stats_layout.addRow("Select an item to view stats", QLabel("")) layout.addWidget(self.stats_group) # Buttons button_box = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel ) button_box.accepted.connect(self.on_accept) button_box.rejected.connect(self.reject) self.ok_btn = button_box.button(QDialogButtonBox.StandardButton.Ok) self.ok_btn.setEnabled(False) self.ok_btn.setText("Select Gear") layout.addWidget(button_box) def load_mock_data(self): """Load mock gear data for testing.""" from core.nexus_api import MOCK_WEAPONS, MOCK_ARMORS, MOCK_TOOLS self.results_tree.clear() if self.gear_type == "weapon": for weapon_id, weapon in MOCK_WEAPONS.items(): item = QTreeWidgetItem([ weapon.name, f"{weapon.damage}", f"{weapon.dpp}", f"{weapon.range}m" ]) item.setData(0, Qt.ItemDataRole.UserRole, { 'id': weapon_id, 'type': 'weapon', 'data': weapon }) self.results_tree.addTopLevelItem(item) elif self.gear_type == "armor": for armor_id, armor in MOCK_ARMORS.items(): total_prot = armor.get_total_protection() item = QTreeWidgetItem([ armor.name, f"{total_prot}", f"{armor.decay_pec} PEC", "-" ]) item.setData(0, Qt.ItemDataRole.UserRole, { 'id': armor_id, 'type': 'armor', 'data': armor }) self.results_tree.addTopLevelItem(item) elif self.gear_type == "tool": for tool_id, tool in MOCK_TOOLS.items(): item = QTreeWidgetItem([ tool.name, f"{tool.depth}m", f"{tool.decay_pec} PEC", f"{tool.radius}m" ]) item.setData(0, Qt.ItemDataRole.UserRole, { 'id': tool_id, 'type': 'tool', 'data': tool }) self.results_tree.addTopLevelItem(item) def on_search(self): """Search for gear.""" query = self.search_input.text().strip().lower() if not query: self.load_mock_data() return # Filter results for i in range(self.results_tree.topLevelItemCount()): item = self.results_tree.topLevelItem(i) name = item.text(0).lower() item.setHidden(query not in name) def on_selection_changed(self): """Handle selection change.""" selected = self.results_tree.selectedItems() if selected: item_data = selected[0].data(0, Qt.ItemDataRole.UserRole) self.selected_gear = item_data self.ok_btn.setEnabled(True) self.update_stats_preview(item_data) else: self.selected_gear = None self.ok_btn.setEnabled(False) def update_stats_preview(self, item_data): """Update stats preview.""" # Clear previous while self.stats_layout.rowCount() > 0: self.stats_layout.removeRow(0) gear = item_data['data'] if item_data['type'] == 'weapon': self.stats_layout.addRow("Damage:", QLabel(f"{gear.damage}")) self.stats_layout.addRow("DPP:", QLabel(f"{gear.dpp}")) self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC")) self.stats_layout.addRow("Ammo:", QLabel(f"{gear.ammo_pec} PEC")) self.stats_layout.addRow("Range:", QLabel(f"{gear.range}m")) self.stats_layout.addRow("Attacks/min:", QLabel(f"{gear.attacks_per_min}")) self.stats_layout.addRow("Cost/hour:", QLabel(f"{gear.calculate_cost_per_hour():.2f} PED")) elif item_data['type'] == 'armor': self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC")) self.stats_layout.addRow("Total Protection:", QLabel(f"{gear.get_total_protection()}")) for dmg_type, value in gear.protection.items(): self.stats_layout.addRow(f" {dmg_type.title()}:", QLabel(f"{value}")) elif item_data['type'] == 'tool': self.stats_layout.addRow("Type:", QLabel(f"{gear.tool_type}")) self.stats_layout.addRow("Depth:", QLabel(f"{gear.depth}m")) self.stats_layout.addRow("Radius:", QLabel(f"{gear.radius}m")) self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC")) if gear.probe_cost: self.stats_layout.addRow("Cost/drop:", QLabel(f"{gear.calculate_cost_per_drop():.3f} PED")) def on_item_double_clicked(self, item, column): """Handle double click.""" self.on_accept() def on_accept(self): """Handle OK button.""" if self.selected_gear: gear = self.selected_gear['data'] self.gear_selected.emit( self.selected_gear['type'], gear.name, { 'id': self.selected_gear['id'], 'dpp': getattr(gear, 'dpp', None), 'cost_per_hour': getattr(gear, 'calculate_cost_per_hour', lambda: 0)() } ) self.accept() if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) dialog = GearSelectorDialog("weapon") if dialog.exec() == QDialog.DialogCode.Accepted: print("Selected gear:", dialog.selected_gear) sys.exit(0)