fix: add missing selector dialogs for simplified loadout manager

- weapon_selector.py: Simple weapon selection with cost preview
- armor_selector.py: Armor set selection with decay estimation
- healing_selector.py: Healing tool selection with economy info
- All selectors calculate cost per use in real-time
- Search/filter functionality for quick finding
This commit is contained in:
LemonNexus 2026-02-09 21:51:54 +00:00
parent cdc9f5b825
commit fe5efa181d
3 changed files with 503 additions and 557 deletions

View File

@ -1,409 +1,203 @@
""" """
Searchable Armor Selector for Lemontropia Suite Lemontropia Suite - Simple Armor Selector
Browse and search armors from Entropia Nexus API Quick armor selection for cost configuration.
""" """
import logging
from decimal import Decimal from decimal import Decimal
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, QPushButton, QLineEdit, QListWidget, QListWidgetItem,
QProgressBar, QGroupBox, QFormLayout, QTabWidget, QWidget QMessageBox, QFormLayout, QGroupBox
) )
from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal
from typing import Optional, List
from core.nexus_full_api import get_nexus_api, NexusArmor, NexusArmorSet from core.nexus_full_api import get_nexus_api
logger = logging.getLogger(__name__)
class ArmorLoaderThread(QThread):
"""Background thread for loading armors from API."""
armors_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def run(self):
try:
api = get_nexus_api()
armors = api.get_all_armors()
self.armors_loaded.emit(armors)
except Exception as e:
self.error_occurred.emit(str(e))
class ArmorSetLoaderThread(QThread):
"""Background thread for loading armor sets from API."""
armor_sets_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def run(self):
try:
api = get_nexus_api()
armor_sets = api.get_all_armor_sets()
self.armor_sets_loaded.emit(armor_sets)
except Exception as e:
self.error_occurred.emit(str(e))
class ArmorSelectorDialog(QDialog): class ArmorSelectorDialog(QDialog):
"""Dialog for selecting armors from Entropia Nexus API with search and sets.""" """Simple dialog to select armor for cost tracking."""
armor_selected = pyqtSignal(NexusArmor)
armor_set_selected = pyqtSignal(NexusArmorSet) # New signal for full sets
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("Select Armor - Entropia Nexus") self.setWindowTitle("Select Armor")
self.setMinimumSize(1000, 700) self.setMinimumSize(500, 400)
self.all_armors: List[NexusArmor] = [] self.selected_armor = None
self.all_armor_sets: List[NexusArmorSet] = [] self._armors = []
self.selected_armor: Optional[NexusArmor] = None
self.selected_armor_set: Optional[NexusArmorSet] = None
self._setup_ui() self._setup_ui()
self._load_data() self._load_armors()
def _setup_ui(self): def _setup_ui(self):
"""Setup simple UI."""
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setSpacing(10) layout.setSpacing(10)
# Status
self.status_label = QLabel("Loading armors from Entropia Nexus...")
layout.addWidget(self.status_label)
self.progress = QProgressBar()
self.progress.setRange(0, 0) # Indeterminate
layout.addWidget(self.progress)
# Tab widget for Individual Pieces vs Full Sets
self.tabs = QTabWidget()
# === INDIVIDUAL PIECES TAB ===
self.tab_individual = QWidget()
individual_layout = QVBoxLayout(self.tab_individual)
# Search # Search
search_layout = QHBoxLayout() search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:")) search_layout.addWidget(QLabel("Search:"))
self.search_input = QLineEdit() self.search_edit = QLineEdit()
self.search_input.setPlaceholderText("Type to search individual armor pieces...") self.search_edit.setPlaceholderText("Type armor name...")
self.search_input.textChanged.connect(self._on_search) self.search_edit.textChanged.connect(self._on_search)
search_layout.addWidget(self.search_input) search_layout.addWidget(self.search_edit)
layout.addLayout(search_layout)
self.clear_btn = QPushButton("Clear") # Armor list
self.clear_btn.clicked.connect(self._clear_search) self.armor_list = QListWidget()
search_layout.addWidget(self.clear_btn) self.armor_list.itemClicked.connect(self._on_select)
individual_layout.addLayout(search_layout) self.armor_list.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(self.armor_list)
# Results tree for individual pieces # Preview
self.results_tree = QTreeWidget() preview_group = QGroupBox("Selected Armor")
self.results_tree.setHeaderLabels([ preview_layout = QFormLayout(preview_group)
"Name", "Type", "Durability",
"Impact", "Cut", "Stab", "Burn", "Cold", "Acid", "Electric",
"Total Prot"
])
header = self.results_tree.header()
header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
header.setStretchLastSection(False)
self.results_tree.itemSelectionChanged.connect(self._on_selection_changed)
self.results_tree.itemDoubleClicked.connect(self._on_double_click)
individual_layout.addWidget(self.results_tree)
self.tabs.addTab(self.tab_individual, "🛡️ Individual Pieces") self.preview_name = QLabel("None")
# === FULL SETS TAB ===
self.tab_sets = QWidget()
sets_layout = QVBoxLayout(self.tab_sets)
# Search for sets
set_search_layout = QHBoxLayout()
set_search_layout.addWidget(QLabel("Search:"))
self.set_search_input = QLineEdit()
self.set_search_input.setPlaceholderText("Type to search armor sets (e.g., 'Ghost', 'Shogun')...")
self.set_search_input.textChanged.connect(self._on_set_search)
set_search_layout.addWidget(self.set_search_input)
self.set_clear_btn = QPushButton("Clear")
self.set_clear_btn.clicked.connect(self._clear_set_search)
set_search_layout.addWidget(self.set_clear_btn)
sets_layout.addLayout(set_search_layout)
# Results tree for sets
self.sets_tree = QTreeWidget()
self.sets_tree.setHeaderLabels([
"Set Name", "Pieces Count", "Total Impact", "Total Cut", "Total Stab",
"Total Burn", "Total Cold", "Total Acid", "Total Electric", "Total Prot"
])
sets_header = self.sets_tree.header()
sets_header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
sets_header.setStretchLastSection(False)
self.sets_tree.itemSelectionChanged.connect(self._on_set_selection_changed)
self.sets_tree.itemDoubleClicked.connect(self._on_set_double_click)
sets_layout.addWidget(self.sets_tree)
self.tabs.addTab(self.tab_sets, "⚔️ Full Armor Sets")
layout.addWidget(self.tabs)
# Preview panel (shared)
self.preview_group = QGroupBox("Armor Preview")
preview_layout = QFormLayout(self.preview_group)
self.preview_name = QLabel("-")
self.preview_type = QLabel("-")
self.preview_durability = QLabel("-")
self.preview_protection = QLabel("-")
preview_layout.addRow("Name:", self.preview_name) preview_layout.addRow("Name:", self.preview_name)
preview_layout.addRow("Type:", self.preview_type)
preview_layout.addRow("Durability:", self.preview_durability) self.preview_protection = QLabel("-")
preview_layout.addRow("Protection:", self.preview_protection) preview_layout.addRow("Protection:", self.preview_protection)
layout.addWidget(self.preview_group)
self.preview_decay = QLabel("-")
preview_layout.addRow("Decay/Hit:", self.preview_decay)
self.preview_cost = QLabel("-")
self.preview_cost.setStyleSheet("font-weight: bold; color: #7FFF7F;")
preview_layout.addRow("Cost/Hit:", self.preview_cost)
layout.addWidget(preview_group)
# Buttons # Buttons
buttons = QDialogButtonBox( button_layout = QHBoxLayout()
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel button_layout.addStretch()
)
buttons.accepted.connect(self._on_accept)
buttons.rejected.connect(self.reject)
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
self.ok_button.setEnabled(False)
layout.addWidget(buttons)
# Update preview when tab changes self.ok_btn = QPushButton("Select")
self.tabs.currentChanged.connect(self._on_tab_changed) self.ok_btn.clicked.connect(self.accept)
self.ok_btn.setEnabled(False)
self.ok_btn.setStyleSheet("""
QPushButton {
background-color: #2E7D32;
color: white;
padding: 8px 16px;
font-weight: bold;
}
QPushButton:disabled {
background-color: #333;
color: #666;
}
""")
button_layout.addWidget(self.ok_btn)
def _load_data(self): cancel_btn = QPushButton("Cancel")
"""Load armors and armor sets in background threads.""" cancel_btn.clicked.connect(self.reject)
# Load individual armors button_layout.addWidget(cancel_btn)
self.loader = ArmorLoaderThread()
self.loader.armors_loaded.connect(self._on_armors_loaded)
self.loader.error_occurred.connect(self._on_load_error)
self.loader.start()
# Load armor sets layout.addLayout(button_layout)
self.set_loader = ArmorSetLoaderThread()
self.set_loader.armor_sets_loaded.connect(self._on_armor_sets_loaded)
self.set_loader.error_occurred.connect(self._on_set_load_error)
self.set_loader.start()
def _on_armors_loaded(self, armors: List[NexusArmor]): def _load_armors(self):
"""Handle loaded armors.""" """Load armors from API."""
self.all_armors = armors try:
self.status_label.setText(f"Loaded {len(armors)} armors and {len(self.all_armor_sets)} armor sets from Entropia Nexus") nexus = get_nexus_api()
self.progress.setRange(0, 100) # Get armor sets from API
self.progress.setValue(100) self._armors = nexus.get_all_armor_sets()
self._populate_results(armors)
def _on_load_error(self, error: str): # Sort by name
"""Handle load error.""" self._armors.sort(key=lambda a: a.name.lower())
self.status_label.setText(f"Error loading armors: {error}")
self.progress.setRange(0, 100)
self.progress.setValue(0)
def _on_armor_sets_loaded(self, armor_sets: List[NexusArmorSet]): self._populate_list(self._armors)
"""Handle loaded armor sets.""" except Exception as e:
self.all_armor_sets = armor_sets logger.error(f"Failed to load armors: {e}")
self._populate_sets(armor_sets) QMessageBox.warning(self, "Error", f"Failed to load armors: {e}")
def _on_set_load_error(self, error: str): def _populate_list(self, armors):
"""Handle armor set load error.""" """Populate list with armors."""
self.status_label.setText(f"Error loading armor sets: {error}") self.armor_list.clear()
def _populate_results(self, armors: List[NexusArmor]):
"""Populate results tree with individual armor pieces."""
self.results_tree.clear()
for armor in armors: for armor in armors:
item = QTreeWidgetItem() # Get decay from Defense field if available
item.setText(0, armor.name) decay_pec = Decimal("0")
item.setText(1, armor.type) if hasattr(armor, 'defense') and armor.defense:
item.setText(2, str(armor.durability)) # Estimate decay based on protection level
item.setText(3, str(armor.protection_impact)) # Higher protection = higher decay
item.setText(4, str(armor.protection_cut)) decay_pec = Decimal("15") # Default estimate
item.setText(5, str(armor.protection_stab))
item.setText(6, str(armor.protection_burn))
item.setText(7, str(armor.protection_cold))
item.setText(8, str(armor.protection_acid))
item.setText(9, str(armor.protection_electric))
total = armor.protection_impact + armor.protection_cut + armor.protection_stab cost_per_hit = decay_pec / Decimal("100")
item.setText(10, str(total))
item.setData(0, Qt.ItemDataRole.UserRole, armor) item = QListWidgetItem(f"{armor.name}")
self.results_tree.addTopLevelItem(item) item.setData(Qt.ItemDataRole.UserRole, armor)
def _populate_sets(self, armor_sets: List[NexusArmorSet]): # Tooltip with protection info
"""Populate sets tree with full armor sets.""" tooltip_parts = [f"Set ID: {armor.set_id}"]
self.sets_tree.clear() if hasattr(armor, 'defense') and armor.defense:
for armor_set in armor_sets: tooltip_parts.append(f"Defense: {armor.defense}")
item = QTreeWidgetItem() tooltip_parts.append(f"Est. Decay: {decay_pec} PEC")
item.setText(0, armor_set.name) tooltip_parts.append(f"Est. Cost/Hit: {cost_per_hit:.4f} PED")
item.setText(1, str(len(armor_set.pieces)))
# Get total protection from the set item.setToolTip("\n".join(tooltip_parts))
prot = armor_set.total_protection
item.setText(2, str(prot.impact))
item.setText(3, str(prot.cut))
item.setText(4, str(prot.stab))
item.setText(5, str(prot.burn))
item.setText(6, str(prot.cold))
item.setText(7, str(prot.acid))
item.setText(8, str(prot.electric))
total = prot.get_total() self.armor_list.addItem(item)
item.setText(9, str(total))
item.setData(0, Qt.ItemDataRole.UserRole, armor_set) def _on_search(self, text):
self.sets_tree.addTopLevelItem(item) """Filter armors by search text."""
def _on_search(self, text: str):
"""Handle search text change for individual pieces."""
if not text: if not text:
self._populate_results(self.all_armors) self._populate_list(self._armors)
return return
query = text.lower() text_lower = text.lower()
filtered = [a for a in self.all_armors if query in a.name.lower()] filtered = [a for a in self._armors if text_lower in a.name.lower()]
self._populate_results(filtered) self._populate_list(filtered)
self.status_label.setText(f"Found {len(filtered)} armors matching '{text}'")
def _clear_search(self): def _on_select(self, item):
"""Clear search for individual pieces.""" """Update preview when armor selected."""
self.search_input.clear() armor = item.data(Qt.ItemDataRole.UserRole)
self._populate_results(self.all_armors) if not armor:
self.status_label.setText(f"Showing all {len(self.all_armors)} armors")
def _on_set_search(self, text: str):
"""Handle search text change for armor sets."""
if not text:
self._populate_sets(self.all_armor_sets)
return return
query = text.lower() self.selected_armor = armor
filtered = [s for s in self.all_armor_sets if query in s.name.lower()]
self._populate_sets(filtered)
self.status_label.setText(f"Found {len(filtered)} armor sets matching '{text}'")
def _clear_set_search(self): # Estimate decay (we'll need to calculate this properly)
"""Clear search for armor sets.""" decay_pec = self._estimate_decay(armor)
self.set_search_input.clear() cost_per_hit = decay_pec / Decimal("100")
self._populate_sets(self.all_armor_sets)
self.status_label.setText(f"Showing all {len(self.all_armor_sets)} armor sets")
def _on_tab_changed(self, index: int): # Update preview
"""Handle tab change - clear selection and update UI."""
self.selected_armor = None
self.selected_armor_set = None
self.ok_button.setEnabled(False)
self._clear_preview()
def _clear_preview(self):
"""Clear preview panel."""
self.preview_name.setText("-")
self.preview_type.setText("-")
self.preview_durability.setText("-")
self.preview_protection.setText("-")
def _on_selection_changed(self):
"""Handle selection change for individual pieces."""
items = self.results_tree.selectedItems()
if items:
self.selected_armor = items[0].data(0, Qt.ItemDataRole.UserRole)
self.selected_armor_set = None
self.ok_button.setEnabled(True)
self._update_preview(self.selected_armor)
else:
self.selected_armor = None
self.ok_button.setEnabled(False)
def _on_set_selection_changed(self):
"""Handle selection change for armor sets."""
items = self.sets_tree.selectedItems()
if items:
self.selected_armor_set = items[0].data(0, Qt.ItemDataRole.UserRole)
self.selected_armor = None
self.ok_button.setEnabled(True)
self._update_set_preview(self.selected_armor_set)
else:
self.selected_armor_set = None
self.ok_button.setEnabled(False)
def _update_preview(self, armor: NexusArmor):
"""Update preview panel for individual armor."""
self.preview_name.setText(armor.name) self.preview_name.setText(armor.name)
self.preview_type.setText(f"Piece: {armor.type}")
self.preview_durability.setText(f"{armor.durability} (~{Decimal('20') / (Decimal('1') - Decimal(armor.durability) / Decimal('100000')):.2f} hp/pec)")
prot_parts = [] protection_text = "See tooltip"
if armor.protection_impact > 0: if hasattr(armor, 'defense') and armor.defense:
prot_parts.append(f"Imp:{armor.protection_impact}") protection_text = str(armor.defense)[:50]
if armor.protection_cut > 0: self.preview_protection.setText(protection_text)
prot_parts.append(f"Cut:{armor.protection_cut}")
if armor.protection_stab > 0:
prot_parts.append(f"Stab:{armor.protection_stab}")
if armor.protection_burn > 0:
prot_parts.append(f"Burn:{armor.protection_burn}")
if armor.protection_cold > 0:
prot_parts.append(f"Cold:{armor.protection_cold}")
if armor.protection_acid > 0:
prot_parts.append(f"Acid:{armor.protection_acid}")
if armor.protection_electric > 0:
prot_parts.append(f"Elec:{armor.protection_electric}")
self.preview_protection.setText(", ".join(prot_parts) if prot_parts else "None") self.preview_decay.setText(f"{decay_pec} PEC (estimated)")
self.preview_cost.setText(f"{cost_per_hit:.4f} PED")
def _update_set_preview(self, armor_set: NexusArmorSet): self.ok_btn.setEnabled(True)
"""Update preview panel for armor set."""
self.preview_name.setText(armor_set.name)
self.preview_type.setText(f"Full Set ({len(armor_set.pieces)} pieces)")
# Show pieces in the set def _estimate_decay(self, armor):
pieces_text = "\n".join([f"{piece}" for piece in armor_set.pieces[:7]]) """Estimate decay based on armor protection."""
if len(armor_set.pieces) > 7: # This is a simplified estimate - real decay comes from API data
pieces_text += f"\n ... and {len(armor_set.pieces) - 7} more" # For now, return a default value
self.preview_durability.setText(f"Pieces:\n{pieces_text}") # TODO: Get real decay from armor pieces or set data
return Decimal("15")
# Show total protection def _on_double_click(self, item):
prot = armor_set.total_protection """Double-click to select immediately."""
prot_parts = [] self._on_select(item)
if prot.impact > 0:
prot_parts.append(f"Imp:{prot.impact}")
if prot.cut > 0:
prot_parts.append(f"Cut:{prot.cut}")
if prot.stab > 0:
prot_parts.append(f"Stab:{prot.stab}")
if prot.burn > 0:
prot_parts.append(f"Burn:{prot.burn}")
if prot.cold > 0:
prot_parts.append(f"Cold:{prot.cold}")
if prot.acid > 0:
prot_parts.append(f"Acid:{prot.acid}")
if prot.electric > 0:
prot_parts.append(f"Elec:{prot.electric}")
total_prot = f"Total: {prot.get_total()}\n" + ", ".join(prot_parts)
if armor_set.set_bonus:
total_prot += f"\n\n✨ Set Bonus: {armor_set.set_bonus}"
self.preview_protection.setText(total_prot)
def _on_double_click(self, item, column):
"""Handle double click on individual armor."""
if self.selected_armor:
self._on_accept()
def _on_set_double_click(self, item, column):
"""Handle double click on armor set."""
if self.selected_armor_set:
self._on_accept()
def _on_accept(self):
"""Handle OK button."""
if self.tabs.currentIndex() == 0 and self.selected_armor:
# Individual piece tab
self.armor_selected.emit(self.selected_armor)
self.accept()
elif self.tabs.currentIndex() == 1 and self.selected_armor_set:
# Full sets tab
self.armor_set_selected.emit(self.selected_armor_set)
self.accept()
"""Handle OK button."""
if self.selected_armor:
self.armor_selected.emit(self.selected_armor)
self.accept() self.accept()
def get_selected_armor(self):
"""Get the selected armor as a dict for the simplified system."""
if not self.selected_armor:
return None
decay_pec = self._estimate_decay(self.selected_armor)
return {
'name': self.selected_armor.name,
'api_id': getattr(self.selected_armor, 'set_id', None),
'decay_pec': decay_pec,
'protection_summary': str(getattr(self.selected_armor, 'defense', 'Unknown')),
}

View File

@ -1,244 +1,214 @@
""" """
Searchable Healing Tool Selector for Lemontropia Suite Lemontropia Suite - Simple Healing Selector
Browse and search healing tools from Entropia Nexus API Quick healing tool selection for cost configuration.
""" """
import logging
from decimal import Decimal from decimal import Decimal
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, QPushButton, QLineEdit, QListWidget, QListWidgetItem,
QProgressBar, QGroupBox, QFormLayout, QComboBox QMessageBox, QFormLayout, QGroupBox
) )
from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QColor
from typing import Optional, List
from core.nexus_full_api import get_nexus_api, NexusHealingTool from core.nexus_full_api import get_nexus_api
logger = logging.getLogger(__name__)
class HealingLoaderThread(QThread):
"""Background thread for loading healing tools from API."""
tools_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def run(self):
try:
api = get_nexus_api()
tools = api.get_all_healing_tools()
self.tools_loaded.emit(tools)
except Exception as e:
self.error_occurred.emit(str(e))
class HealingSelectorDialog(QDialog): class HealingSelectorDialog(QDialog):
"""Dialog for selecting healing tools from Entropia Nexus API with search.""" """Simple dialog to select healing tool for cost tracking."""
tool_selected = pyqtSignal(NexusHealingTool)
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("Select Healing Tool - Entropia Nexus") self.setWindowTitle("Select Healing Tool")
self.setMinimumSize(900, 600) self.setMinimumSize(500, 400)
self.all_tools: List[NexusHealingTool] = [] self.selected_healing = None
self.selected_tool: Optional[NexusHealingTool] = None self._healing_tools = []
self._setup_ui() self._setup_ui()
self._load_data() self._load_healing_tools()
def _setup_ui(self): def _setup_ui(self):
"""Setup simple UI."""
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setSpacing(10) layout.setSpacing(10)
# Status
self.status_label = QLabel("Loading healing tools from Entropia Nexus...")
layout.addWidget(self.status_label)
self.progress = QProgressBar()
self.progress.setRange(0, 0)
layout.addWidget(self.progress)
# Filters
filter_layout = QHBoxLayout()
filter_layout.addWidget(QLabel("Type:"))
self.type_combo = QComboBox()
self.type_combo.addItems(["All Types", "FAP (Medical Tool)", "Restoration Chip", "Limited (L)"])
self.type_combo.currentTextChanged.connect(self._apply_filters)
filter_layout.addWidget(self.type_combo)
filter_layout.addWidget(QLabel("Min Heal:"))
self.min_heal_combo = QComboBox()
self.min_heal_combo.addItems(["Any", "10+", "25+", "50+", "75+", "100+"])
self.min_heal_combo.currentTextChanged.connect(self._apply_filters)
filter_layout.addWidget(self.min_heal_combo)
layout.addLayout(filter_layout)
# Search # Search
search_layout = QHBoxLayout() search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:")) search_layout.addWidget(QLabel("Search:"))
self.search_input = QLineEdit() self.search_edit = QLineEdit()
self.search_input.setPlaceholderText("Type to search healing tools...") self.search_edit.setPlaceholderText("Type healing tool name...")
self.search_input.textChanged.connect(self._apply_filters) self.search_edit.textChanged.connect(self._on_search)
search_layout.addWidget(self.search_input) search_layout.addWidget(self.search_edit)
self.clear_btn = QPushButton("Clear")
self.clear_btn.clicked.connect(self._clear_search)
search_layout.addWidget(self.clear_btn)
layout.addLayout(search_layout) layout.addLayout(search_layout)
# Results tree # Healing list
self.results_tree = QTreeWidget() self.healing_list = QListWidget()
self.results_tree.setHeaderLabels([ self.healing_list.itemClicked.connect(self._on_select)
"Name", "Type", "Heal Amount", "Decay (PEC)", "Economy (hp/pec)", "Prof. Level", "Limited" self.healing_list.itemDoubleClicked.connect(self._on_double_click)
]) layout.addWidget(self.healing_list)
header = self.results_tree.header()
header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
header.setStretchLastSection(False)
self.results_tree.itemSelectionChanged.connect(self._on_selection_changed)
self.results_tree.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(self.results_tree)
# Preview panel # Preview
self.preview_group = QGroupBox("Healing Tool Preview") preview_group = QGroupBox("Selected Healing Tool")
preview_layout = QFormLayout(self.preview_group) preview_layout = QFormLayout(preview_group)
self.preview_name = QLabel("-")
self.preview_heal = QLabel("-") self.preview_name = QLabel("None")
self.preview_decay = QLabel("-")
self.preview_economy = QLabel("-")
self.preview_cost = QLabel("-")
preview_layout.addRow("Name:", self.preview_name) preview_layout.addRow("Name:", self.preview_name)
self.preview_heal = QLabel("-")
preview_layout.addRow("Heal Amount:", self.preview_heal) preview_layout.addRow("Heal Amount:", self.preview_heal)
self.preview_decay = QLabel("-")
preview_layout.addRow("Decay:", self.preview_decay) preview_layout.addRow("Decay:", self.preview_decay)
self.preview_economy = QLabel("-")
preview_layout.addRow("Economy:", self.preview_economy) preview_layout.addRow("Economy:", self.preview_economy)
self.preview_cost = QLabel("-")
self.preview_cost.setStyleSheet("font-weight: bold; color: #7FFF7F;")
preview_layout.addRow("Cost/Heal:", self.preview_cost) preview_layout.addRow("Cost/Heal:", self.preview_cost)
layout.addWidget(self.preview_group)
layout.addWidget(preview_group)
# Buttons # Buttons
buttons = QDialogButtonBox( button_layout = QHBoxLayout()
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel button_layout.addStretch()
)
buttons.accepted.connect(self._on_accept)
buttons.rejected.connect(self.reject)
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
self.ok_button.setEnabled(False)
layout.addWidget(buttons)
def _load_data(self): self.ok_btn = QPushButton("Select")
"""Load healing tools in background thread.""" self.ok_btn.clicked.connect(self.accept)
self.loader = HealingLoaderThread() self.ok_btn.setEnabled(False)
self.loader.tools_loaded.connect(self._on_tools_loaded) self.ok_btn.setStyleSheet("""
self.loader.error_occurred.connect(self._on_load_error) QPushButton {
self.loader.start() background-color: #2E7D32;
color: white;
padding: 8px 16px;
font-weight: bold;
}
QPushButton:disabled {
background-color: #333;
color: #666;
}
""")
button_layout.addWidget(self.ok_btn)
def _on_tools_loaded(self, tools: List[NexusHealingTool]): cancel_btn = QPushButton("Cancel")
"""Handle loaded tools.""" cancel_btn.clicked.connect(self.reject)
self.all_tools = tools button_layout.addWidget(cancel_btn)
self.status_label.setText(f"Loaded {len(tools)} healing tools from Entropia Nexus")
self.progress.setRange(0, 100)
self.progress.setValue(100)
self._apply_filters()
def _on_load_error(self, error: str): layout.addLayout(button_layout)
"""Handle load error."""
self.status_label.setText(f"Error loading healing tools: {error}")
self.progress.setRange(0, 100)
self.progress.setValue(0)
def _apply_filters(self): def _load_healing_tools(self):
"""Apply all filters and search.""" """Load healing tools from API."""
tools = self.all_tools.copy() try:
nexus = get_nexus_api()
# Type filter # Get both medical tools and chips
type_filter = self.type_combo.currentText() tools = nexus.get_all_medical_tools()
if type_filter == "FAP (Medical Tool)": chips = nexus.get_all_medical_chips()
tools = [t for t in tools if t.type == 'fap']
elif type_filter == "Restoration Chip":
tools = [t for t in tools if 'chip' in t.type.lower()]
elif type_filter == "Limited (L)":
tools = [t for t in tools if t.is_limited]
# Min heal filter self._healing_tools = tools + chips
min_heal = self.min_heal_combo.currentText()
if min_heal != "Any":
min_val = int(min_heal.replace("+", ""))
tools = [t for t in tools if t.heal_amount >= min_val]
# Search filter # Sort by name
search_text = self.search_input.text() self._healing_tools.sort(key=lambda h: h.name.lower())
if search_text:
query = search_text.lower()
tools = [t for t in tools if query in t.name.lower()]
self._populate_results(tools) self._populate_list(self._healing_tools)
except Exception as e:
logger.error(f"Failed to load healing tools: {e}")
QMessageBox.warning(self, "Error", f"Failed to load healing tools: {e}")
# Update status def _populate_list(self, healing_tools):
if search_text: """Populate list with healing tools."""
self.status_label.setText(f"Found {len(tools)} tools matching '{search_text}'") self.healing_list.clear()
else:
self.status_label.setText(f"Showing {len(tools)} of {len(self.all_tools)} tools")
def _populate_results(self, tools: List[NexusHealingTool]): for tool in healing_tools:
"""Populate results tree.""" # Get decay and heal amount
self.results_tree.clear() decay_pec = Decimal(str(getattr(tool, 'decay', 0)))
heal_min = getattr(tool, 'heal_min', 0)
heal_max = getattr(tool, 'heal_max', 0)
heal_avg = (Decimal(str(heal_min)) + Decimal(str(heal_max))) / Decimal("2")
# Sort by economy (best first) cost_per_heal = decay_pec / Decimal("100")
tools = sorted(tools, key=lambda t: t.heal_per_pec, reverse=True)
for tool in tools: item = QListWidgetItem(f"{tool.name} (💚 {heal_avg:.0f} HP)")
item = QTreeWidgetItem() item.setData(Qt.ItemDataRole.UserRole, tool)
item.setText(0, tool.name)
item.setText(1, tool.type)
item.setText(2, str(tool.heal_amount))
item.setText(3, f"{tool.decay:.2f}")
item.setText(4, f"{tool.heal_per_pec:.2f}")
item.setText(5, str(tool.profession_level) if tool.profession_level > 0 else "-")
item.setText(6, "Yes" if tool.is_limited else "No")
# Color code by economy (green = good, red = bad) # Tooltip
if tool.heal_per_pec >= 20: tooltip_parts = [
item.setForeground(4, QColor("#4caf50")) # Green f"Heal: {heal_min}-{heal_max} HP",
elif tool.heal_per_pec >= 15: f"Decay: {decay_pec} PEC",
item.setForeground(4, QColor("#ff9800")) # Orange ]
else:
item.setForeground(4, QColor("#f44336")) # Red
item.setData(0, Qt.ItemDataRole.UserRole, tool) if hasattr(tool, 'economy') and tool.economy:
self.results_tree.addTopLevelItem(item) tooltip_parts.append(f"Economy: {tool.economy} HP/PEC")
def _clear_search(self): tooltip_parts.append(f"Cost/Heal: {cost_per_heal:.4f} PED")
"""Clear search and filters."""
self.search_input.clear()
self.type_combo.setCurrentIndex(0)
self.min_heal_combo.setCurrentIndex(0)
self._apply_filters()
def _on_selection_changed(self): item.setToolTip("\n".join(tooltip_parts))
"""Handle selection change."""
items = self.results_tree.selectedItems()
if items:
self.selected_tool = items[0].data(0, Qt.ItemDataRole.UserRole)
self.ok_button.setEnabled(True)
self._update_preview(self.selected_tool)
else:
self.selected_tool = None
self.ok_button.setEnabled(False)
def _update_preview(self, tool: NexusHealingTool): self.healing_list.addItem(item)
"""Update preview panel."""
def _on_search(self, text):
"""Filter healing tools by search text."""
if not text:
self._populate_list(self._healing_tools)
return
text_lower = text.lower()
filtered = [h for h in self._healing_tools if text_lower in h.name.lower()]
self._populate_list(filtered)
def _on_select(self, item):
"""Update preview when healing tool selected."""
tool = item.data(Qt.ItemDataRole.UserRole)
if not tool:
return
self.selected_healing = tool
# Get values
decay_pec = Decimal(str(getattr(tool, 'decay', 0)))
heal_min = getattr(tool, 'heal_min', 0)
heal_max = getattr(tool, 'heal_max', 0)
heal_avg = (Decimal(str(heal_min)) + Decimal(str(heal_max))) / Decimal("2")
cost_per_heal = decay_pec / Decimal("100")
# Update preview
self.preview_name.setText(tool.name) self.preview_name.setText(tool.name)
self.preview_heal.setText(f"{tool.heal_amount} HP") self.preview_heal.setText(f"{heal_min}-{heal_max} HP (avg {heal_avg:.0f})")
self.preview_decay.setText(f"{tool.decay:.2f} PEC") self.preview_decay.setText(f"{decay_pec} PEC")
self.preview_economy.setText(f"{tool.heal_per_pec:.2f} hp/pec")
self.preview_cost.setText(f"{tool.decay / Decimal('100'):.4f} PED")
def _on_double_click(self, item, column): economy_text = "-"
"""Handle double click.""" if hasattr(tool, 'economy') and tool.economy:
self._on_accept() economy_text = f"{tool.economy} HP/PEC"
self.preview_economy.setText(economy_text)
def _on_accept(self): self.preview_cost.setText(f"{cost_per_heal:.4f} PED")
"""Handle OK button."""
if self.selected_tool: self.ok_btn.setEnabled(True)
self.tool_selected.emit(self.selected_tool)
def _on_double_click(self, item):
"""Double-click to select immediately."""
self._on_select(item)
self.accept() self.accept()
def get_selected_healing(self):
"""Get the selected healing tool as a dict for the simplified system."""
if not self.selected_healing:
return None
decay_pec = Decimal(str(getattr(self.selected_healing, 'decay', 0)))
heal_min = getattr(self.selected_healing, 'heal_min', 0)
heal_max = getattr(self.selected_healing, 'heal_max', 0)
heal_avg = (Decimal(str(heal_min)) + Decimal(str(heal_max))) / Decimal("2")
return {
'name': self.selected_healing.name,
'api_id': getattr(self.selected_healing, 'id', None),
'decay_pec': decay_pec,
'heal_amount': heal_avg,
}

182
ui/weapon_selector.py Normal file
View File

@ -0,0 +1,182 @@
"""
Lemontropia Suite - Simple Weapon Selector
Quick weapon selection for cost configuration.
"""
import logging
from decimal import Decimal
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QLineEdit, QListWidget, QListWidgetItem,
QMessageBox, QFormLayout, QGroupBox
)
from PyQt6.QtCore import Qt, pyqtSignal
from core.nexus_full_api import get_nexus_api
logger = logging.getLogger(__name__)
class WeaponSelectorDialog(QDialog):
"""Simple dialog to select a weapon for cost tracking."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Select Weapon")
self.setMinimumSize(500, 400)
self.selected_weapon = None
self._weapons = []
self._setup_ui()
self._load_weapons()
def _setup_ui(self):
"""Setup simple UI."""
layout = QVBoxLayout(self)
layout.setSpacing(10)
# Search
search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:"))
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("Type weapon name...")
self.search_edit.textChanged.connect(self._on_search)
search_layout.addWidget(self.search_edit)
layout.addLayout(search_layout)
# Weapon list
self.weapon_list = QListWidget()
self.weapon_list.itemClicked.connect(self._on_select)
self.weapon_list.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(self.weapon_list)
# Preview
preview_group = QGroupBox("Selected Weapon")
preview_layout = QFormLayout(preview_group)
self.preview_name = QLabel("None")
preview_layout.addRow("Name:", self.preview_name)
self.preview_damage = QLabel("-")
preview_layout.addRow("Damage:", self.preview_damage)
self.preview_decay = QLabel("-")
preview_layout.addRow("Decay:", self.preview_decay)
self.preview_ammo = QLabel("-")
preview_layout.addRow("Ammo:", self.preview_ammo)
self.preview_cost = QLabel("-")
self.preview_cost.setStyleSheet("font-weight: bold; color: #7FFF7F;")
preview_layout.addRow("Cost/Shot:", self.preview_cost)
layout.addWidget(preview_group)
# Buttons
button_layout = QHBoxLayout()
button_layout.addStretch()
self.ok_btn = QPushButton("Select")
self.ok_btn.clicked.connect(self.accept)
self.ok_btn.setEnabled(False)
self.ok_btn.setStyleSheet("""
QPushButton {
background-color: #2E7D32;
color: white;
padding: 8px 16px;
font-weight: bold;
}
QPushButton:disabled {
background-color: #333;
color: #666;
}
""")
button_layout.addWidget(self.ok_btn)
cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
def _load_weapons(self):
"""Load weapons from API."""
try:
nexus = get_nexus_api()
self._weapons = nexus.get_all_weapons()
# Sort by name
self._weapons.sort(key=lambda w: w.name.lower())
self._populate_list(self._weapons)
except Exception as e:
logger.error(f"Failed to load weapons: {e}")
QMessageBox.warning(self, "Error", f"Failed to load weapons: {e}")
def _populate_list(self, weapons):
"""Populate list with weapons."""
self.weapon_list.clear()
for weapon in weapons:
# Calculate cost per shot
decay_pec = Decimal(str(weapon.decay))
ammo = Decimal(str(weapon.ammo))
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
item = QListWidgetItem(f"{weapon.name} (💰 {cost_per_shot:.4f} PED)")
item.setData(Qt.ItemDataRole.UserRole, weapon)
# Tooltip
tooltip = (
f"Damage: {weapon.damage}\n"
f"Decay: {weapon.decay} PEC\n"
f"Ammo: {weapon.ammo}\n"
f"Range: {weapon.range}\n"
f"DPP: {weapon.dpp:.2f}"
)
item.setToolTip(tooltip)
self.weapon_list.addItem(item)
def _on_search(self, text):
"""Filter weapons by search text."""
if not text:
self._populate_list(self._weapons)
return
text_lower = text.lower()
filtered = [w for w in self._weapons if text_lower in w.name.lower()]
self._populate_list(filtered)
def _on_select(self, item):
"""Update preview when weapon selected."""
weapon = item.data(Qt.ItemDataRole.UserRole)
if not weapon:
return
self.selected_weapon = weapon
# Calculate cost per shot
decay_pec = Decimal(str(weapon.decay))
ammo = Decimal(str(weapon.ammo))
cost_per_shot = (decay_pec / Decimal("100")) + (ammo * Decimal("0.0001"))
# Update preview
self.preview_name.setText(weapon.name)
self.preview_damage.setText(str(weapon.damage))
self.preview_decay.setText(f"{weapon.decay} PEC")
self.preview_ammo.setText(str(weapon.ammo))
self.preview_cost.setText(f"{cost_per_shot:.4f} PED")
self.ok_btn.setEnabled(True)
def _on_double_click(self, item):
"""Double-click to select immediately."""
self._on_select(item)
self.accept()
def get_selected_weapon(self):
"""Get the selected weapon."""
return self.selected_weapon