341 lines
13 KiB
Python
341 lines
13 KiB
Python
"""
|
|
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, MedicalTool
|
|
|
|
|
|
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 MedicalToolLoaderThread(QThread):
|
|
"""Thread to load medical tools (FAPs) from API."""
|
|
|
|
medical_tools_loaded = pyqtSignal(list)
|
|
error_occurred = pyqtSignal(str)
|
|
|
|
def run(self):
|
|
try:
|
|
api = EntropiaNexusAPI()
|
|
tools = api.get_all_medical_tools()
|
|
self.medical_tools_loaded.emit(tools)
|
|
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),
|
|
'decay': float(w.decay) if w.decay else 0,
|
|
'ammo_burn': w.ammo_burn or 0,
|
|
})
|
|
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)
|
|
|