From fe5efa181d09ba29686a8b68958f4b9923f87ae8 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Mon, 9 Feb 2026 21:51:54 +0000 Subject: [PATCH] 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 --- ui/armor_selector.py | 508 ++++++++++++----------------------------- ui/healing_selector.py | 370 ++++++++++++++---------------- ui/weapon_selector.py | 182 +++++++++++++++ 3 files changed, 503 insertions(+), 557 deletions(-) create mode 100644 ui/weapon_selector.py diff --git a/ui/armor_selector.py b/ui/armor_selector.py index a9929f5..43e42fb 100644 --- a/ui/armor_selector.py +++ b/ui/armor_selector.py @@ -1,409 +1,203 @@ """ -Searchable Armor Selector for Lemontropia Suite -Browse and search armors from Entropia Nexus API +Lemontropia Suite - Simple Armor Selector +Quick armor selection for cost configuration. """ +import logging from decimal import Decimal + from PyQt6.QtWidgets import ( - QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, - QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, - QProgressBar, QGroupBox, QFormLayout, QTabWidget, QWidget + QDialog, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QLineEdit, QListWidget, QListWidgetItem, + QMessageBox, QFormLayout, QGroupBox ) -from PyQt6.QtCore import Qt, QThread, pyqtSignal -from typing import Optional, List +from PyQt6.QtCore import Qt, pyqtSignal -from core.nexus_full_api import get_nexus_api, NexusArmor, NexusArmorSet +from core.nexus_full_api import get_nexus_api - -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)) +logger = logging.getLogger(__name__) class ArmorSelectorDialog(QDialog): - """Dialog for selecting armors from Entropia Nexus API with search and sets.""" - - armor_selected = pyqtSignal(NexusArmor) - armor_set_selected = pyqtSignal(NexusArmorSet) # New signal for full sets + """Simple dialog to select armor for cost tracking.""" def __init__(self, parent=None): super().__init__(parent) - self.setWindowTitle("Select Armor - Entropia Nexus") - self.setMinimumSize(1000, 700) + self.setWindowTitle("Select Armor") + self.setMinimumSize(500, 400) - self.all_armors: List[NexusArmor] = [] - self.all_armor_sets: List[NexusArmorSet] = [] - self.selected_armor: Optional[NexusArmor] = None - self.selected_armor_set: Optional[NexusArmorSet] = None + self.selected_armor = None + self._armors = [] self._setup_ui() - self._load_data() + self._load_armors() def _setup_ui(self): + """Setup simple UI.""" layout = QVBoxLayout(self) 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_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("Type to search individual armor pieces...") - self.search_input.textChanged.connect(self._on_search) - search_layout.addWidget(self.search_input) + self.search_edit = QLineEdit() + self.search_edit.setPlaceholderText("Type armor name...") + self.search_edit.textChanged.connect(self._on_search) + search_layout.addWidget(self.search_edit) + layout.addLayout(search_layout) - self.clear_btn = QPushButton("Clear") - self.clear_btn.clicked.connect(self._clear_search) - search_layout.addWidget(self.clear_btn) - individual_layout.addLayout(search_layout) + # Armor list + self.armor_list = QListWidget() + self.armor_list.itemClicked.connect(self._on_select) + self.armor_list.itemDoubleClicked.connect(self._on_double_click) + layout.addWidget(self.armor_list) - # Results tree for individual pieces - self.results_tree = QTreeWidget() - self.results_tree.setHeaderLabels([ - "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) + # Preview + preview_group = QGroupBox("Selected Armor") + preview_layout = QFormLayout(preview_group) - self.tabs.addTab(self.tab_individual, "šŸ›”ļø Individual Pieces") - - # === 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("-") + self.preview_name = QLabel("None") 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) - 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 = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel - ) - 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) + button_layout = QHBoxLayout() + button_layout.addStretch() - # Update preview when tab changes - self.tabs.currentChanged.connect(self._on_tab_changed) - - def _load_data(self): - """Load armors and armor sets in background threads.""" - # Load individual armors - self.loader = ArmorLoaderThread() - self.loader.armors_loaded.connect(self._on_armors_loaded) - self.loader.error_occurred.connect(self._on_load_error) - self.loader.start() + 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) - # Load armor sets - 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() + cancel_btn = QPushButton("Cancel") + cancel_btn.clicked.connect(self.reject) + button_layout.addWidget(cancel_btn) + + layout.addLayout(button_layout) - def _on_armors_loaded(self, armors: List[NexusArmor]): - """Handle loaded armors.""" - self.all_armors = armors - self.status_label.setText(f"Loaded {len(armors)} armors and {len(self.all_armor_sets)} armor sets from Entropia Nexus") - self.progress.setRange(0, 100) - self.progress.setValue(100) - self._populate_results(armors) + def _load_armors(self): + """Load armors from API.""" + try: + nexus = get_nexus_api() + # Get armor sets from API + self._armors = nexus.get_all_armor_sets() + + # 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): - """Handle load error.""" - 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]): - """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() + def _populate_list(self, armors): + """Populate list with armors.""" + self.armor_list.clear() + for armor in armors: - item = QTreeWidgetItem() - item.setText(0, armor.name) - item.setText(1, armor.type) - item.setText(2, str(armor.durability)) - item.setText(3, str(armor.protection_impact)) - item.setText(4, str(armor.protection_cut)) - 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)) + # Get decay from Defense field if available + decay_pec = Decimal("0") + if hasattr(armor, 'defense') and armor.defense: + # Estimate decay based on protection level + # Higher protection = higher decay + decay_pec = Decimal("15") # Default estimate - total = armor.protection_impact + armor.protection_cut + armor.protection_stab - item.setText(10, str(total)) + cost_per_hit = decay_pec / Decimal("100") - item.setData(0, Qt.ItemDataRole.UserRole, armor) - self.results_tree.addTopLevelItem(item) + item = QListWidgetItem(f"{armor.name}") + 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]): - """Populate sets tree with full armor sets.""" - 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.""" + def _on_search(self, text): + """Filter armors by search text.""" if not text: - self._populate_results(self.all_armors) + self._populate_list(self._armors) return - query = text.lower() - filtered = [a for a in self.all_armors if query in a.name.lower()] - self._populate_results(filtered) - self.status_label.setText(f"Found {len(filtered)} armors matching '{text}'") + text_lower = text.lower() + filtered = [a for a in self._armors if text_lower in a.name.lower()] + self._populate_list(filtered) - def _clear_search(self): - """Clear search for individual pieces.""" - self.search_input.clear() - self._populate_results(self.all_armors) - 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) + def _on_select(self, item): + """Update preview when armor selected.""" + armor = item.data(Qt.ItemDataRole.UserRole) + if not armor: return - query = text.lower() - 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): - """Clear search for armor sets.""" - 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.selected_armor = armor + + # Estimate decay (we'll need to calculate this properly) + decay_pec = self._estimate_decay(armor) + cost_per_hit = decay_pec / Decimal("100") + + # Update preview 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 = [] - if armor.protection_impact > 0: - prot_parts.append(f"Imp:{armor.protection_impact}") - if armor.protection_cut > 0: - 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}") + protection_text = "See tooltip" + if hasattr(armor, 'defense') and armor.defense: + protection_text = str(armor.defense)[:50] + self.preview_protection.setText(protection_text) - 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): - """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 - 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 _estimate_decay(self, armor): + """Estimate decay based on armor protection.""" + # This is a simplified estimate - real decay comes from API data + # For now, return a default value + # TODO: Get real decay from armor pieces or set data + return Decimal("15") - def _on_double_click(self, item, column): - """Handle double click on individual armor.""" - if self.selected_armor: - self._on_accept() + def _on_double_click(self, item): + """Double-click to select immediately.""" + self._on_select(item) + self.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() + 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')), + } diff --git a/ui/healing_selector.py b/ui/healing_selector.py index 278417c..59012a8 100644 --- a/ui/healing_selector.py +++ b/ui/healing_selector.py @@ -1,244 +1,214 @@ """ -Searchable Healing Tool Selector for Lemontropia Suite -Browse and search healing tools from Entropia Nexus API +Lemontropia Suite - Simple Healing Selector +Quick healing tool selection for cost configuration. """ +import logging from decimal import Decimal + from PyQt6.QtWidgets import ( - QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, - QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox, - QProgressBar, QGroupBox, QFormLayout, QComboBox + QDialog, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QLineEdit, QListWidget, QListWidgetItem, + QMessageBox, QFormLayout, QGroupBox ) -from PyQt6.QtCore import Qt, QThread, pyqtSignal -from PyQt6.QtGui import QColor -from typing import Optional, List +from PyQt6.QtCore import Qt, pyqtSignal -from core.nexus_full_api import get_nexus_api, NexusHealingTool +from core.nexus_full_api import get_nexus_api - -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)) +logger = logging.getLogger(__name__) class HealingSelectorDialog(QDialog): - """Dialog for selecting healing tools from Entropia Nexus API with search.""" - - tool_selected = pyqtSignal(NexusHealingTool) + """Simple dialog to select healing tool for cost tracking.""" def __init__(self, parent=None): super().__init__(parent) - self.setWindowTitle("Select Healing Tool - Entropia Nexus") - self.setMinimumSize(900, 600) + self.setWindowTitle("Select Healing Tool") + self.setMinimumSize(500, 400) - self.all_tools: List[NexusHealingTool] = [] - self.selected_tool: Optional[NexusHealingTool] = None + self.selected_healing = None + self._healing_tools = [] self._setup_ui() - self._load_data() + self._load_healing_tools() def _setup_ui(self): + """Setup simple UI.""" layout = QVBoxLayout(self) 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_layout = QHBoxLayout() search_layout.addWidget(QLabel("Search:")) - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("Type to search healing tools...") - self.search_input.textChanged.connect(self._apply_filters) - search_layout.addWidget(self.search_input) - - self.clear_btn = QPushButton("Clear") - self.clear_btn.clicked.connect(self._clear_search) - search_layout.addWidget(self.clear_btn) + self.search_edit = QLineEdit() + self.search_edit.setPlaceholderText("Type healing tool name...") + self.search_edit.textChanged.connect(self._on_search) + search_layout.addWidget(self.search_edit) layout.addLayout(search_layout) - # Results tree - self.results_tree = QTreeWidget() - self.results_tree.setHeaderLabels([ - "Name", "Type", "Heal Amount", "Decay (PEC)", "Economy (hp/pec)", "Prof. Level", "Limited" - ]) - 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) + # Healing list + self.healing_list = QListWidget() + self.healing_list.itemClicked.connect(self._on_select) + self.healing_list.itemDoubleClicked.connect(self._on_double_click) + layout.addWidget(self.healing_list) - # Preview panel - self.preview_group = QGroupBox("Healing Tool Preview") - preview_layout = QFormLayout(self.preview_group) - self.preview_name = QLabel("-") - self.preview_heal = QLabel("-") - self.preview_decay = QLabel("-") - self.preview_economy = QLabel("-") - self.preview_cost = QLabel("-") + # Preview + preview_group = QGroupBox("Selected Healing Tool") + preview_layout = QFormLayout(preview_group) + + self.preview_name = QLabel("None") preview_layout.addRow("Name:", self.preview_name) + + self.preview_heal = QLabel("-") preview_layout.addRow("Heal Amount:", self.preview_heal) + + self.preview_decay = QLabel("-") preview_layout.addRow("Decay:", self.preview_decay) + + self.preview_economy = QLabel("-") 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) - layout.addWidget(self.preview_group) + + layout.addWidget(preview_group) # Buttons - buttons = QDialogButtonBox( - QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel - ) - 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) + 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_data(self): - """Load healing tools in background thread.""" - self.loader = HealingLoaderThread() - self.loader.tools_loaded.connect(self._on_tools_loaded) - 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") + def _load_healing_tools(self): + """Load healing tools from API.""" + try: + nexus = get_nexus_api() - # Color code by economy (green = good, red = bad) - if tool.heal_per_pec >= 20: - item.setForeground(4, QColor("#4caf50")) # Green - elif tool.heal_per_pec >= 15: - item.setForeground(4, QColor("#ff9800")) # Orange - else: - item.setForeground(4, QColor("#f44336")) # Red + # Get both medical tools and chips + tools = nexus.get_all_medical_tools() + chips = nexus.get_all_medical_chips() - item.setData(0, Qt.ItemDataRole.UserRole, tool) - self.results_tree.addTopLevelItem(item) + self._healing_tools = tools + chips + + # 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): - """Clear search and filters.""" - self.search_input.clear() - self.type_combo.setCurrentIndex(0) - self.min_heal_combo.setCurrentIndex(0) - self._apply_filters() + def _populate_list(self, healing_tools): + """Populate list with healing tools.""" + self.healing_list.clear() + + for tool in healing_tools: + # 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): - """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 _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 _update_preview(self, tool: NexusHealingTool): - """Update preview panel.""" + 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_heal.setText(f"{tool.heal_amount} HP") - self.preview_decay.setText(f"{tool.decay:.2f} PEC") - self.preview_economy.setText(f"{tool.heal_per_pec:.2f} hp/pec") - self.preview_cost.setText(f"{tool.decay / Decimal('100'):.4f} PED") + self.preview_heal.setText(f"{heal_min}-{heal_max} HP (avg {heal_avg:.0f})") + self.preview_decay.setText(f"{decay_pec} PEC") + + 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): - """Handle double click.""" - self._on_accept() + def _on_double_click(self, item): + """Double-click to select immediately.""" + self._on_select(item) + self.accept() - def _on_accept(self): - """Handle OK button.""" - if self.selected_tool: - self.tool_selected.emit(self.selected_tool) - 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, + } diff --git a/ui/weapon_selector.py b/ui/weapon_selector.py new file mode 100644 index 0000000..acc59c0 --- /dev/null +++ b/ui/weapon_selector.py @@ -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