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, QDialogButtonBox, QTabWidget, QGroupBox, QFormLayout,
QMessageBox, QHeaderView QMessageBox, QHeaderView
) )
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal, QThread
from decimal import Decimal from decimal import Decimal
import asyncio
from typing import Optional, List 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): class GearSelectorDialog(QDialog):
@ -23,43 +45,200 @@ class GearSelectorDialog(QDialog):
def __init__(self, gear_type: str = "weapon", parent=None): def __init__(self, gear_type: str = "weapon", parent=None):
super().__init__(parent) super().__init__(parent)
self.gear_type = gear_type # weapon, armor, tool self.gear_type = gear_type
self.selected_gear = None self.selected_gear = None
self.weapons: List[WeaponStats] = []
self.api = EntropiaNexusAPI()
self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus") self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus")
self.setMinimumSize(700, 500) self.setMinimumSize(800, 600)
self.setup_ui() self.setup_ui()
# Load initial data # Load weapons in background
self.load_mock_data() # Use mock data for now self.load_weapons_async()
def setup_ui(self): def setup_ui(self):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setSpacing(10) layout.setSpacing(10)
# Status label
self.status_label = QLabel("Loading weapons from Entropia Nexus...")
layout.addWidget(self.status_label)
# Search box # Search box
search_layout = QHBoxLayout() search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:")) search_layout.addWidget(QLabel("Search:"))
self.search_input = QLineEdit() 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.returnPressed.connect(self.on_search)
self.search_input.setEnabled(False)
search_layout.addWidget(self.search_input) search_layout.addWidget(self.search_input)
self.search_btn = QPushButton("Search") self.search_btn = QPushButton("Search")
self.search_btn.clicked.connect(self.on_search) self.search_btn.clicked.connect(self.on_search)
self.search_btn.setEnabled(False)
search_layout.addWidget(self.search_btn) search_layout.addWidget(self.search_btn)
layout.addLayout(search_layout) layout.addLayout(search_layout)
# Results list # Results list
self.results_tree = QTreeWidget() 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.setAlternatingRowColors(True)
self.results_tree.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection) self.results_tree.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection)
self.results_tree.itemSelectionChanged.connect(self.on_selection_changed) self.results_tree.itemSelectionChanged.connect(self.on_selection_changed)
self.results_tree.itemDoubleClicked.connect(self.on_item_double_clicked) 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 # Adjust columns
header = self.results_tree.header() header = self.results_tree.header()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)