feat(gui): add Gear Selector and fix kill counting
- Remove auto-kill counting on loot (was overcounting) - Add GearSelectorDialog to select weapons/armor/tools from Nexus API - Add Tools → Select Gear menu (Ctrl+G) - Selected weapon now shows in HUD during session - Uses mock data from nexus_api.py for now
This commit is contained in:
parent
0f4d1271f8
commit
b8e7a892ff
|
|
@ -0,0 +1,230 @@
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
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 # weapon, armor, tool
|
||||||
|
self.selected_gear = None
|
||||||
|
|
||||||
|
self.setWindowTitle(f"Select {gear_type.title()} - Entropia Nexus")
|
||||||
|
self.setMinimumSize(700, 500)
|
||||||
|
self.setup_ui()
|
||||||
|
|
||||||
|
# Load initial data
|
||||||
|
self.load_mock_data() # Use mock data for now
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# 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.returnPressed.connect(self.on_search)
|
||||||
|
search_layout.addWidget(self.search_input)
|
||||||
|
|
||||||
|
self.search_btn = QPushButton("Search")
|
||||||
|
self.search_btn.clicked.connect(self.on_search)
|
||||||
|
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.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.resizeSection(1, 120)
|
||||||
|
header.resizeSection(2, 100)
|
||||||
|
header.resizeSection(3, 100)
|
||||||
|
|
||||||
|
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("Select Gear")
|
||||||
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
|
def load_mock_data(self):
|
||||||
|
"""Load mock gear data for testing."""
|
||||||
|
from core.nexus_api import MOCK_WEAPONS, MOCK_ARMORS, MOCK_TOOLS
|
||||||
|
|
||||||
|
self.results_tree.clear()
|
||||||
|
|
||||||
|
if self.gear_type == "weapon":
|
||||||
|
for weapon_id, weapon in MOCK_WEAPONS.items():
|
||||||
|
item = QTreeWidgetItem([
|
||||||
|
weapon.name,
|
||||||
|
f"{weapon.damage}",
|
||||||
|
f"{weapon.dpp}",
|
||||||
|
f"{weapon.range}m"
|
||||||
|
])
|
||||||
|
item.setData(0, Qt.ItemDataRole.UserRole, {
|
||||||
|
'id': weapon_id,
|
||||||
|
'type': 'weapon',
|
||||||
|
'data': weapon
|
||||||
|
})
|
||||||
|
self.results_tree.addTopLevelItem(item)
|
||||||
|
|
||||||
|
elif self.gear_type == "armor":
|
||||||
|
for armor_id, armor in MOCK_ARMORS.items():
|
||||||
|
total_prot = armor.get_total_protection()
|
||||||
|
item = QTreeWidgetItem([
|
||||||
|
armor.name,
|
||||||
|
f"{total_prot}",
|
||||||
|
f"{armor.decay_pec} PEC",
|
||||||
|
"-"
|
||||||
|
])
|
||||||
|
item.setData(0, Qt.ItemDataRole.UserRole, {
|
||||||
|
'id': armor_id,
|
||||||
|
'type': 'armor',
|
||||||
|
'data': armor
|
||||||
|
})
|
||||||
|
self.results_tree.addTopLevelItem(item)
|
||||||
|
|
||||||
|
elif self.gear_type == "tool":
|
||||||
|
for tool_id, tool in MOCK_TOOLS.items():
|
||||||
|
item = QTreeWidgetItem([
|
||||||
|
tool.name,
|
||||||
|
f"{tool.depth}m",
|
||||||
|
f"{tool.decay_pec} PEC",
|
||||||
|
f"{tool.radius}m"
|
||||||
|
])
|
||||||
|
item.setData(0, Qt.ItemDataRole.UserRole, {
|
||||||
|
'id': tool_id,
|
||||||
|
'type': 'tool',
|
||||||
|
'data': tool
|
||||||
|
})
|
||||||
|
self.results_tree.addTopLevelItem(item)
|
||||||
|
|
||||||
|
def on_search(self):
|
||||||
|
"""Search for gear."""
|
||||||
|
query = self.search_input.text().strip().lower()
|
||||||
|
if not query:
|
||||||
|
self.load_mock_data()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Filter results
|
||||||
|
for i in range(self.results_tree.topLevelItemCount()):
|
||||||
|
item = self.results_tree.topLevelItem(i)
|
||||||
|
name = item.text(0).lower()
|
||||||
|
item.setHidden(query not in name)
|
||||||
|
|
||||||
|
def on_selection_changed(self):
|
||||||
|
"""Handle selection change."""
|
||||||
|
selected = self.results_tree.selectedItems()
|
||||||
|
if selected:
|
||||||
|
item_data = selected[0].data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
self.selected_gear = item_data
|
||||||
|
self.ok_btn.setEnabled(True)
|
||||||
|
self.update_stats_preview(item_data)
|
||||||
|
else:
|
||||||
|
self.selected_gear = None
|
||||||
|
self.ok_btn.setEnabled(False)
|
||||||
|
|
||||||
|
def update_stats_preview(self, item_data):
|
||||||
|
"""Update stats preview."""
|
||||||
|
# Clear previous
|
||||||
|
while self.stats_layout.rowCount() > 0:
|
||||||
|
self.stats_layout.removeRow(0)
|
||||||
|
|
||||||
|
gear = item_data['data']
|
||||||
|
|
||||||
|
if item_data['type'] == 'weapon':
|
||||||
|
self.stats_layout.addRow("Damage:", QLabel(f"{gear.damage}"))
|
||||||
|
self.stats_layout.addRow("DPP:", QLabel(f"{gear.dpp}"))
|
||||||
|
self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC"))
|
||||||
|
self.stats_layout.addRow("Ammo:", QLabel(f"{gear.ammo_pec} PEC"))
|
||||||
|
self.stats_layout.addRow("Range:", QLabel(f"{gear.range}m"))
|
||||||
|
self.stats_layout.addRow("Attacks/min:", QLabel(f"{gear.attacks_per_min}"))
|
||||||
|
self.stats_layout.addRow("Cost/hour:", QLabel(f"{gear.calculate_cost_per_hour():.2f} PED"))
|
||||||
|
|
||||||
|
elif item_data['type'] == 'armor':
|
||||||
|
self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC"))
|
||||||
|
self.stats_layout.addRow("Total Protection:", QLabel(f"{gear.get_total_protection()}"))
|
||||||
|
for dmg_type, value in gear.protection.items():
|
||||||
|
self.stats_layout.addRow(f" {dmg_type.title()}:", QLabel(f"{value}"))
|
||||||
|
|
||||||
|
elif item_data['type'] == 'tool':
|
||||||
|
self.stats_layout.addRow("Type:", QLabel(f"{gear.tool_type}"))
|
||||||
|
self.stats_layout.addRow("Depth:", QLabel(f"{gear.depth}m"))
|
||||||
|
self.stats_layout.addRow("Radius:", QLabel(f"{gear.radius}m"))
|
||||||
|
self.stats_layout.addRow("Decay:", QLabel(f"{gear.decay_pec} PEC"))
|
||||||
|
if gear.probe_cost:
|
||||||
|
self.stats_layout.addRow("Cost/drop:", QLabel(f"{gear.calculate_cost_per_drop():.3f} 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:
|
||||||
|
gear = self.selected_gear['data']
|
||||||
|
self.gear_selected.emit(
|
||||||
|
self.selected_gear['type'],
|
||||||
|
gear.name,
|
||||||
|
{
|
||||||
|
'id': self.selected_gear['id'],
|
||||||
|
'dpp': getattr(gear, 'dpp', None),
|
||||||
|
'cost_per_hour': getattr(gear, 'calculate_cost_per_hour', lambda: 0)()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
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 gear:", dialog.selected_gear)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
@ -587,9 +587,9 @@ class HUDOverlay(QWidget):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._stats.loot_total += value_ped
|
self._stats.loot_total += value_ped
|
||||||
# Count actual loot as kills (exclude Shrapnel and Universal Ammo)
|
# Note: Kill counting should be based on actual kill messages from log
|
||||||
if item_name not in ('Shrapnel', 'Universal Ammo'):
|
# For now, we don't auto-increment kills on loot to avoid overcounting
|
||||||
self._stats.kills += 1
|
# (Multiple items from one kill would count as multiple kills)
|
||||||
|
|
||||||
self._refresh_display()
|
self._refresh_display()
|
||||||
self.stats_updated.emit(self._stats.to_dict())
|
self.stats_updated.emit(self._stats.to_dict())
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,10 @@ class MainWindow(QMainWindow):
|
||||||
self.current_session_id: Optional[int] = None
|
self.current_session_id: Optional[int] = None
|
||||||
self._current_db_session_id: Optional[int] = None
|
self._current_db_session_id: Optional[int] = None
|
||||||
|
|
||||||
|
# Selected gear
|
||||||
|
self._selected_weapon: Optional[str] = None
|
||||||
|
self._selected_weapon_stats: Optional[dict] = None
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
self.apply_dark_theme()
|
self.apply_dark_theme()
|
||||||
|
|
@ -533,6 +537,13 @@ class MainWindow(QMainWindow):
|
||||||
# Tools menu
|
# Tools menu
|
||||||
tools_menu = menubar.addMenu("&Tools")
|
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)
|
||||||
|
|
||||||
|
tools_menu.addSeparator()
|
||||||
|
|
||||||
loadout_action = QAction("&Loadout Manager", self)
|
loadout_action = QAction("&Loadout Manager", self)
|
||||||
loadout_action.setShortcut("Ctrl+L")
|
loadout_action.setShortcut("Ctrl+L")
|
||||||
loadout_action.triggered.connect(self.on_loadout_manager)
|
loadout_action.triggered.connect(self.on_loadout_manager)
|
||||||
|
|
@ -899,8 +910,9 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
# Show HUD and start session tracking
|
# Show HUD and start session tracking
|
||||||
self.hud.show()
|
self.hud.show()
|
||||||
self.hud.start_session(weapon="Unknown", loadout="Default")
|
weapon_name = self._selected_weapon or "Unknown"
|
||||||
self.log_info("HUD", "HUD overlay shown and session tracking started")
|
self.hud.start_session(weapon=weapon_name, loadout="Default")
|
||||||
|
self.log_info("HUD", f"HUD shown - Weapon: {weapon_name}")
|
||||||
|
|
||||||
def _setup_log_watcher_callbacks(self):
|
def _setup_log_watcher_callbacks(self):
|
||||||
"""Setup LogWatcher event callbacks."""
|
"""Setup LogWatcher event callbacks."""
|
||||||
|
|
@ -1244,6 +1256,27 @@ class MainWindow(QMainWindow):
|
||||||
dialog = LoadoutManagerDialog(self)
|
dialog = LoadoutManagerDialog(self)
|
||||||
dialog.exec()
|
dialog.exec()
|
||||||
|
|
||||||
|
def on_select_gear(self):
|
||||||
|
"""Open Gear Selector dialog."""
|
||||||
|
from ui.gear_selector import GearSelectorDialog
|
||||||
|
|
||||||
|
# For now, default to weapon selection
|
||||||
|
dialog = GearSelectorDialog("weapon", 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
|
||||||
|
})
|
||||||
|
|
||||||
def on_about(self):
|
def on_about(self):
|
||||||
"""Show about dialog."""
|
"""Show about dialog."""
|
||||||
QMessageBox.about(
|
QMessageBox.about(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue