feat(gui): full gear selection - weapons, armors, and finders
- Gear Selector now supports: Weapons, Armors, Finders - Weapons: 3,099 items with DPP, cost/hour, damage - Armors: 1,985 items with protection values - Finders: 106 items with depth/radius - Menu: Tools → Select Gear → Weapon/Armor/Finder - Shortcuts: Ctrl+W (weapon), Ctrl+Shift+A (armor), Ctrl+Shift+F (finder) - Sync API for simpler code
This commit is contained in:
parent
6130cfcd28
commit
3cd0613e10
|
|
@ -13,7 +13,7 @@ from PyQt6.QtCore import Qt, pyqtSignal, QThread
|
|||
from decimal import Decimal
|
||||
|
||||
from typing import Optional, List
|
||||
from core.nexus_api import EntropiaNexusAPI, WeaponStats
|
||||
from core.nexus_api import EntropiaNexusAPI, WeaponStats, ArmorStats, FinderStats
|
||||
|
||||
|
||||
class WeaponLoaderThread(QThread):
|
||||
|
|
@ -22,18 +22,41 @@ class WeaponLoaderThread(QThread):
|
|||
weapons_loaded = pyqtSignal(list)
|
||||
error_occurred = pyqtSignal(str)
|
||||
|
||||
def __init__(self, api: EntropiaNexusAPI):
|
||||
super().__init__()
|
||||
self.api = api
|
||||
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:
|
||||
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()
|
||||
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))
|
||||
|
||||
|
|
@ -47,7 +70,254 @@ class GearSelectorDialog(QDialog):
|
|||
super().__init__(parent)
|
||||
self.gear_type = gear_type
|
||||
self.selected_gear = None
|
||||
self.weapons: List[WeaponStats] = []
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -254,6 +254,10 @@ class MainWindow(QMainWindow):
|
|||
# Selected gear
|
||||
self._selected_weapon: Optional[str] = None
|
||||
self._selected_weapon_stats: Optional[dict] = None
|
||||
self._selected_armor: Optional[str] = None
|
||||
self._selected_armor_stats: Optional[dict] = None
|
||||
self._selected_finder: Optional[str] = None
|
||||
self._selected_finder_stats: Optional[dict] = None
|
||||
|
||||
# Setup UI
|
||||
self.setup_ui()
|
||||
|
|
@ -537,10 +541,23 @@ class MainWindow(QMainWindow):
|
|||
# Tools menu
|
||||
tools_menu = menubar.addMenu("&Tools")
|
||||
|
||||
gear_action = QAction("&Select Gear", self)
|
||||
gear_action.setShortcut("Ctrl+G")
|
||||
gear_action.triggered.connect(self.on_select_gear)
|
||||
tools_menu.addAction(gear_action)
|
||||
# Select Gear submenu
|
||||
select_gear_menu = tools_menu.addMenu("Select &Gear")
|
||||
|
||||
select_weapon_action = QAction("&Weapon", self)
|
||||
select_weapon_action.setShortcut("Ctrl+W")
|
||||
select_weapon_action.triggered.connect(lambda: self.on_select_gear("weapon"))
|
||||
select_gear_menu.addAction(select_weapon_action)
|
||||
|
||||
select_armor_action = QAction("&Armor", self)
|
||||
select_armor_action.setShortcut("Ctrl+Shift+A")
|
||||
select_armor_action.triggered.connect(lambda: self.on_select_gear("armor"))
|
||||
select_gear_menu.addAction(select_armor_action)
|
||||
|
||||
select_finder_action = QAction("&Finder", self)
|
||||
select_finder_action.setShortcut("Ctrl+Shift+F")
|
||||
select_finder_action.triggered.connect(lambda: self.on_select_gear("finder"))
|
||||
select_gear_menu.addAction(select_finder_action)
|
||||
|
||||
tools_menu.addSeparator()
|
||||
|
||||
|
|
@ -1256,26 +1273,29 @@ class MainWindow(QMainWindow):
|
|||
dialog = LoadoutManagerDialog(self)
|
||||
dialog.exec()
|
||||
|
||||
def on_select_gear(self):
|
||||
def on_select_gear(self, gear_type: str = "weapon"):
|
||||
"""Open Gear Selector dialog."""
|
||||
from ui.gear_selector import GearSelectorDialog
|
||||
|
||||
# For now, default to weapon selection
|
||||
dialog = GearSelectorDialog("weapon", self)
|
||||
dialog = GearSelectorDialog(gear_type, self)
|
||||
dialog.gear_selected.connect(self.on_gear_selected)
|
||||
dialog.exec()
|
||||
|
||||
def on_gear_selected(self, gear_type: str, name: str, stats: dict):
|
||||
"""Handle gear selection."""
|
||||
self._selected_weapon = name
|
||||
self._selected_weapon_stats = stats
|
||||
self.log_info("Gear", f"Selected {gear_type}: {name}")
|
||||
|
||||
# If session is active, update HUD
|
||||
if self.session_state == SessionState.RUNNING:
|
||||
self.hud.update_stats({
|
||||
'weapon': name
|
||||
})
|
||||
if gear_type == "weapon":
|
||||
self._selected_weapon = name
|
||||
self._selected_weapon_stats = stats
|
||||
if self.session_state == SessionState.RUNNING:
|
||||
self.hud.update_stats({'weapon': name})
|
||||
elif gear_type == "armor":
|
||||
self._selected_armor = name
|
||||
self._selected_armor_stats = stats
|
||||
elif gear_type == "finder":
|
||||
self._selected_finder = name
|
||||
self._selected_finder_stats = stats
|
||||
|
||||
def on_about(self):
|
||||
"""Show about dialog."""
|
||||
|
|
|
|||
Loading…
Reference in New Issue