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:
parent
cdc9f5b825
commit
fe5efa181d
|
|
@ -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')),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue