feat(gui): update Gear Selector to use real Nexus API

- Loads 3099+ real weapons from api.entropianexus.com
- Shows: Name, Type, Damage, DPP, Cost/hour, Efficiency
- Async loading in background thread (no UI freeze)
- Search by name
- Detailed stats preview
- Ready for production use
This commit is contained in:
LemonNexus 2026-02-08 23:04:22 +00:00
parent d07c43ce97
commit f8ddb8f650
1 changed files with 187 additions and 8 deletions

View File

@ -9,11 +9,33 @@ from PyQt6.QtWidgets import (
QDialogButtonBox, QTabWidget, QGroupBox, QFormLayout,
QMessageBox, QHeaderView
)
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtCore import Qt, pyqtSignal, QThread
from decimal import Decimal
import asyncio
from typing import Optional, List
from core.nexus_api import EntropiaNexusAPI, WeaponStats
class WeaponLoaderThread(QThread):
"""Thread to load weapons from API without blocking UI."""
weapons_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def __init__(self, api: EntropiaNexusAPI):
super().__init__()
self.api = api
def run(self):
try:
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
weapons = loop.run_until_complete(self.api.get_all_weapons())
self.weapons_loaded.emit(weapons)
loop.close()
except Exception as e:
self.error_occurred.emit(str(e))
class GearSelectorDialog(QDialog):
@ -23,43 +45,200 @@ class GearSelectorDialog(QDialog):
def __init__(self, gear_type: str = "weapon", parent=None):
super().__init__(parent)
self.gear_type = gear_type # weapon, armor, tool
self.gear_type = gear_type
self.selected_gear = None
self.weapons: List[WeaponStats] = []
self.api = EntropiaNexusAPI()
self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus")
self.setMinimumSize(700, 500)
self.setMinimumSize(800, 600)
self.setup_ui()
# Load initial data
self.load_mock_data() # Use mock data for now
# 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(f"Search for {self.gear_type}...")
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", "Damage/Protection", "DPP/Cost", "Range/Depth"])
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)