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)
def _load_data(self): self.ok_btn.setStyleSheet("""
"""Load armors and armor sets in background threads.""" QPushButton {
# Load individual armors background-color: #2E7D32;
self.loader = ArmorLoaderThread() color: white;
self.loader.armors_loaded.connect(self._on_armors_loaded) padding: 8px 16px;
self.loader.error_occurred.connect(self._on_load_error) font-weight: bold;
self.loader.start() }
QPushButton:disabled {
background-color: #333;
color: #666;
}
""")
button_layout.addWidget(self.ok_btn)
# Load armor sets cancel_btn = QPushButton("Cancel")
self.set_loader = ArmorSetLoaderThread() cancel_btn.clicked.connect(self.reject)
self.set_loader.armor_sets_loaded.connect(self._on_armor_sets_loaded) button_layout.addWidget(cancel_btn)
self.set_loader.error_occurred.connect(self._on_set_load_error)
self.set_loader.start() layout.addLayout(button_layout)
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)
# Sort by name
self._armors.sort(key=lambda a: a.name.lower())
self._populate_list(self._armors)
except Exception as e:
logger.error(f"Failed to load armors: {e}")
QMessageBox.warning(self, "Error", f"Failed to load armors: {e}")
def _on_load_error(self, error: str): def _populate_list(self, armors):
"""Handle load error.""" """Populate list with armors."""
self.status_label.setText(f"Error loading armors: {error}") self.armor_list.clear()
self.progress.setRange(0, 100)
self.progress.setValue(0)
def _on_armor_sets_loaded(self, armor_sets: List[NexusArmorSet]):
"""Handle loaded armor sets."""
self.all_armor_sets = armor_sets
self._populate_sets(armor_sets)
def _on_set_load_error(self, error: str):
"""Handle armor set load error."""
self.status_label.setText(f"Error loading armor sets: {error}")
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)
# Tooltip with protection info
tooltip_parts = [f"Set ID: {armor.set_id}"]
if hasattr(armor, 'defense') and armor.defense:
tooltip_parts.append(f"Defense: {armor.defense}")
tooltip_parts.append(f"Est. Decay: {decay_pec} PEC")
tooltip_parts.append(f"Est. Cost/Hit: {cost_per_hit:.4f} PED")
item.setToolTip("\n".join(tooltip_parts))
self.armor_list.addItem(item)
def _populate_sets(self, armor_sets: List[NexusArmorSet]): def _on_search(self, text):
"""Populate sets tree with full armor sets.""" """Filter armors by search text."""
self.sets_tree.clear()
for armor_set in armor_sets:
item = QTreeWidgetItem()
item.setText(0, armor_set.name)
item.setText(1, str(len(armor_set.pieces)))
# Get total protection from the set
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()
item.setText(9, str(total))
item.setData(0, Qt.ItemDataRole.UserRole, armor_set)
self.sets_tree.addTopLevelItem(item)
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) # Estimate decay (we'll need to calculate this properly)
self.status_label.setText(f"Found {len(filtered)} armor sets matching '{text}'") decay_pec = self._estimate_decay(armor)
cost_per_hit = decay_pec / Decimal("100")
def _clear_set_search(self):
"""Clear search for armor sets.""" # Update preview
self.set_search_input.clear()
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):
"""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")
self.ok_btn.setEnabled(True)
def _update_set_preview(self, armor_set: NexusArmorSet): def _estimate_decay(self, armor):
"""Update preview panel for armor set.""" """Estimate decay based on armor protection."""
self.preview_name.setText(armor_set.name) # This is a simplified estimate - real decay comes from API data
self.preview_type.setText(f"Full Set ({len(armor_set.pieces)} pieces)") # For now, return a default value
# TODO: Get real decay from armor pieces or set data
# Show pieces in the set return Decimal("15")
pieces_text = "\n".join([f"{piece}" for piece in armor_set.pieces[:7]])
if len(armor_set.pieces) > 7:
pieces_text += f"\n ... and {len(armor_set.pieces) - 7} more"
self.preview_durability.setText(f"Pieces:\n{pieces_text}")
# Show total protection
prot = armor_set.total_protection
prot_parts = []
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): def _on_double_click(self, item):
"""Handle double click on individual armor.""" """Double-click to select immediately."""
if self.selected_armor: self._on_select(item)
self._on_accept() self.accept()
def _on_set_double_click(self, item, column): def get_selected_armor(self):
"""Handle double click on armor set.""" """Get the selected armor as a dict for the simplified system."""
if self.selected_armor_set: if not self.selected_armor:
self._on_accept() return None
def _on_accept(self): decay_pec = self._estimate_decay(self.selected_armor)
"""Handle OK button."""
if self.tabs.currentIndex() == 0 and self.selected_armor: return {
# Individual piece tab 'name': self.selected_armor.name,
self.armor_selected.emit(self.selected_armor) 'api_id': getattr(self.selected_armor, 'set_id', None),
self.accept() 'decay_pec': decay_pec,
elif self.tabs.currentIndex() == 1 and self.selected_armor_set: 'protection_summary': str(getattr(self.selected_armor, 'defense', 'Unknown')),
# 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()

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) self.ok_btn = QPushButton("Select")
buttons.rejected.connect(self.reject) self.ok_btn.clicked.connect(self.accept)
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok) self.ok_btn.setEnabled(False)
self.ok_button.setEnabled(False) self.ok_btn.setStyleSheet("""
layout.addWidget(buttons) 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_data(self): def _load_healing_tools(self):
"""Load healing tools in background thread.""" """Load healing tools from API."""
self.loader = HealingLoaderThread() try:
self.loader.tools_loaded.connect(self._on_tools_loaded) nexus = get_nexus_api()
self.loader.error_occurred.connect(self._on_load_error)
self.loader.start()
def _on_tools_loaded(self, tools: List[NexusHealingTool]):
"""Handle loaded tools."""
self.all_tools = tools
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):
"""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):
"""Apply all filters and search."""
tools = self.all_tools.copy()
# Type filter
type_filter = self.type_combo.currentText()
if type_filter == "FAP (Medical Tool)":
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
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
search_text = self.search_input.text()
if search_text:
query = search_text.lower()
tools = [t for t in tools if query in t.name.lower()]
self._populate_results(tools)
# Update status
if search_text:
self.status_label.setText(f"Found {len(tools)} tools matching '{search_text}'")
else:
self.status_label.setText(f"Showing {len(tools)} of {len(self.all_tools)} tools")
def _populate_results(self, tools: List[NexusHealingTool]):
"""Populate results tree."""
self.results_tree.clear()
# Sort by economy (best first)
tools = sorted(tools, key=lambda t: t.heal_per_pec, reverse=True)
for tool in tools:
item = QTreeWidgetItem()
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) # Get both medical tools and chips
if tool.heal_per_pec >= 20: tools = nexus.get_all_medical_tools()
item.setForeground(4, QColor("#4caf50")) # Green chips = nexus.get_all_medical_chips()
elif tool.heal_per_pec >= 15:
item.setForeground(4, QColor("#ff9800")) # Orange
else:
item.setForeground(4, QColor("#f44336")) # Red
item.setData(0, Qt.ItemDataRole.UserRole, tool) self._healing_tools = tools + chips
self.results_tree.addTopLevelItem(item)
# Sort by name
self._healing_tools.sort(key=lambda h: h.name.lower())
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}")
def _clear_search(self): def _populate_list(self, healing_tools):
"""Clear search and filters.""" """Populate list with healing tools."""
self.search_input.clear() self.healing_list.clear()
self.type_combo.setCurrentIndex(0)
self.min_heal_combo.setCurrentIndex(0) for tool in healing_tools:
self._apply_filters() # Get decay and heal amount
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")
item = QListWidgetItem(f"{tool.name} (💚 {heal_avg:.0f} HP)")
item.setData(Qt.ItemDataRole.UserRole, tool)
# Tooltip
tooltip_parts = [
f"Heal: {heal_min}-{heal_max} HP",
f"Decay: {decay_pec} PEC",
]
if hasattr(tool, 'economy') and tool.economy:
tooltip_parts.append(f"Economy: {tool.economy} HP/PEC")
tooltip_parts.append(f"Cost/Heal: {cost_per_heal:.4f} PED")
item.setToolTip("\n".join(tooltip_parts))
self.healing_list.addItem(item)
def _on_selection_changed(self): def _on_search(self, text):
"""Handle selection change.""" """Filter healing tools by search text."""
items = self.results_tree.selectedItems() if not text:
if items: self._populate_list(self._healing_tools)
self.selected_tool = items[0].data(0, Qt.ItemDataRole.UserRole) return
self.ok_button.setEnabled(True)
self._update_preview(self.selected_tool) text_lower = text.lower()
else: filtered = [h for h in self._healing_tools if text_lower in h.name.lower()]
self.selected_tool = None self._populate_list(filtered)
self.ok_button.setEnabled(False)
def _update_preview(self, tool: NexusHealingTool): def _on_select(self, item):
"""Update preview panel.""" """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") economy_text = "-"
if hasattr(tool, 'economy') and tool.economy:
economy_text = f"{tool.economy} HP/PEC"
self.preview_economy.setText(economy_text)
self.preview_cost.setText(f"{cost_per_heal:.4f} PED")
self.ok_btn.setEnabled(True)
def _on_double_click(self, item, column): def _on_double_click(self, item):
"""Handle double click.""" """Double-click to select immediately."""
self._on_accept() self._on_select(item)
self.accept()
def _on_accept(self): def get_selected_healing(self):
"""Handle OK button.""" """Get the selected healing tool as a dict for the simplified system."""
if self.selected_tool: if not self.selected_healing:
self.tool_selected.emit(self.selected_tool) return None
self.accept()
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