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:
parent
d07c43ce97
commit
f8ddb8f650
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue