679 lines
26 KiB
Python
679 lines
26 KiB
Python
"""
|
|
Attachment Selector Dialog for Lemontropia Suite
|
|
UI for selecting and managing gear attachments.
|
|
"""
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QListWidget, QListWidgetItem, QDialogButtonBox, QFormLayout,
|
|
QTabWidget, QWidget
|
|
)
|
|
from PyQt6.QtCore import Qt, pyqtSignal
|
|
from PyQt6.QtGui import QFont
|
|
from decimal import Decimal
|
|
from typing import Optional, List, Dict
|
|
|
|
from core.attachments import (
|
|
Attachment, WeaponAmplifier, WeaponScope, WeaponAbsorber,
|
|
ArmorPlating, FinderAmplifier, Enhancer, MindforceImplant,
|
|
get_mock_attachments
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Data Class for Attachment Configuration
|
|
# ============================================================================
|
|
|
|
class AttachmentConfig:
|
|
"""Configuration for an equipped attachment."""
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
item_id: str,
|
|
attachment_type: str,
|
|
decay_pec: Decimal,
|
|
damage_bonus: Decimal = Decimal("0"),
|
|
range_bonus: Decimal = Decimal("0"),
|
|
efficiency_bonus: Decimal = Decimal("0"),
|
|
protection_bonus: Optional[Dict[str, Decimal]] = None,
|
|
extra_data: Optional[Dict] = None
|
|
):
|
|
self.name = name
|
|
self.item_id = item_id
|
|
self.attachment_type = attachment_type
|
|
self.decay_pec = decay_pec
|
|
self.damage_bonus = damage_bonus
|
|
self.range_bonus = range_bonus
|
|
self.efficiency_bonus = efficiency_bonus
|
|
self.protection_bonus = protection_bonus or {}
|
|
self.extra_data = extra_data or {}
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert to dictionary for JSON serialization."""
|
|
return {
|
|
'name': self.name,
|
|
'item_id': self.item_id,
|
|
'attachment_type': self.attachment_type,
|
|
'decay_pec': str(self.decay_pec),
|
|
'damage_bonus': str(self.damage_bonus),
|
|
'range_bonus': str(self.range_bonus),
|
|
'efficiency_bonus': str(self.efficiency_bonus),
|
|
'protection_bonus': {k: str(v) for k, v in self.protection_bonus.items()},
|
|
'extra_data': self.extra_data,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> 'AttachmentConfig':
|
|
"""Create from dictionary."""
|
|
return cls(
|
|
name=data['name'],
|
|
item_id=data['item_id'],
|
|
attachment_type=data['attachment_type'],
|
|
decay_pec=Decimal(data['decay_pec']),
|
|
damage_bonus=Decimal(data.get('damage_bonus', '0')),
|
|
range_bonus=Decimal(data.get('range_bonus', '0')),
|
|
efficiency_bonus=Decimal(data.get('efficiency_bonus', '0')),
|
|
protection_bonus={k: Decimal(v) for k, v in data.get('protection_bonus', {}).items()},
|
|
extra_data=data.get('extra_data', {}),
|
|
)
|
|
|
|
def get_cost_per_hour(self, uses_per_hour: int = 3600) -> Decimal:
|
|
"""Calculate cost per hour in PED."""
|
|
return (self.decay_pec * Decimal(uses_per_hour)) / Decimal("100")
|
|
|
|
def get_tooltip_text(self) -> str:
|
|
"""Get tooltip text describing the attachment."""
|
|
lines = [f"Decay: {self.decay_pec} PEC/shot"]
|
|
|
|
if self.damage_bonus > 0:
|
|
lines.append(f"Damage: +{self.damage_bonus}")
|
|
if self.range_bonus > 0:
|
|
lines.append(f"Range: +{self.range_bonus}m")
|
|
if self.efficiency_bonus > 0:
|
|
lines.append(f"Efficiency: +{self.efficiency_bonus}%")
|
|
|
|
if self.protection_bonus:
|
|
prots = [f"{k}: +{v}" for k, v in self.protection_bonus.items() if v > 0]
|
|
if prots:
|
|
lines.append("Protection: " + ", ".join(prots))
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
# ============================================================================
|
|
# Attachment Selector Dialog
|
|
# ============================================================================
|
|
|
|
class AttachmentSelectorDialog(QDialog):
|
|
"""
|
|
Dialog for selecting attachments.
|
|
|
|
Supports all attachment types:
|
|
- amplifier: Weapon amplifiers (damage boost)
|
|
- scope: Weapon scopes (range boost)
|
|
- absorber: Weapon absorbers (damage reduction)
|
|
- plating: Armor plating (protection boost)
|
|
- finder_amp: Mining finder amplifiers
|
|
- enhancer: Gear enhancers
|
|
- implant: Mindforce implants
|
|
"""
|
|
|
|
attachment_selected = pyqtSignal(object) # AttachmentConfig or None
|
|
|
|
# Friendly names for attachment types
|
|
TYPE_NAMES = {
|
|
'amplifier': 'Weapon Amplifier',
|
|
'scope': 'Weapon Scope',
|
|
'absorber': 'Weapon Absorber',
|
|
'plating': 'Armor Plating',
|
|
'finder_amp': 'Finder Amplifier',
|
|
'enhancer': 'Enhancer',
|
|
'implant': 'Mindforce Implant',
|
|
}
|
|
|
|
# Icons for attachment types
|
|
TYPE_ICONS = {
|
|
'amplifier': '⚡',
|
|
'scope': '🔭',
|
|
'absorber': '🛡️',
|
|
'plating': '🔩',
|
|
'finder_amp': '⛏️',
|
|
'enhancer': '✨',
|
|
'implant': '🧠',
|
|
}
|
|
|
|
def __init__(self, attachment_type: str, parent=None, allow_none: bool = True):
|
|
"""
|
|
Initialize attachment selector.
|
|
|
|
Args:
|
|
attachment_type: Type of attachment to select
|
|
parent: Parent widget
|
|
allow_none: Whether to allow selecting "None"
|
|
"""
|
|
super().__init__(parent)
|
|
self.attachment_type = attachment_type
|
|
self.selected_attachment = None
|
|
self.allow_none = allow_none
|
|
|
|
type_name = self.TYPE_NAMES.get(attachment_type, attachment_type.title())
|
|
self.setWindowTitle(f"Select {type_name}")
|
|
self.setMinimumSize(500, 450)
|
|
|
|
self._setup_ui()
|
|
self._load_attachments()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup the UI."""
|
|
layout = QVBoxLayout(self)
|
|
layout.setSpacing(10)
|
|
|
|
# Header
|
|
type_name = self.TYPE_NAMES.get(self.attachment_type, self.attachment_type.title())
|
|
type_icon = self.TYPE_ICONS.get(self.attachment_type, '📎')
|
|
|
|
header = QLabel(f"{type_icon} Select {type_name}")
|
|
header.setFont(QFont("Arial", 14, QFont.Weight.Bold))
|
|
header.setStyleSheet("color: #4a90d9; padding: 5px;")
|
|
layout.addWidget(header)
|
|
|
|
# Description
|
|
desc = self._get_type_description()
|
|
if desc:
|
|
desc_label = QLabel(desc)
|
|
desc_label.setStyleSheet("color: #888888; padding-bottom: 10px;")
|
|
desc_label.setWordWrap(True)
|
|
layout.addWidget(desc_label)
|
|
|
|
# Attachment list
|
|
self.list_widget = QListWidget()
|
|
self.list_widget.setAlternatingRowColors(True)
|
|
self.list_widget.setStyleSheet("""
|
|
QListWidget {
|
|
background-color: #2d2d2d;
|
|
color: #e0e0e0;
|
|
border: 1px solid #3d3d3d;
|
|
border-radius: 4px;
|
|
}
|
|
QListWidget::item:selected {
|
|
background-color: #4a90d9;
|
|
}
|
|
QListWidget::item:hover {
|
|
background-color: #3d3d3d;
|
|
}
|
|
""")
|
|
self.list_widget.itemSelectionChanged.connect(self._on_selection_changed)
|
|
self.list_widget.itemDoubleClicked.connect(self._on_double_click)
|
|
layout.addWidget(self.list_widget)
|
|
|
|
# Stats preview
|
|
self.preview_group = QWidget()
|
|
preview_layout = QFormLayout(self.preview_group)
|
|
preview_layout.setContentsMargins(10, 10, 10, 10)
|
|
self.preview_group.setStyleSheet("""
|
|
QWidget {
|
|
background-color: #2d2d2d;
|
|
border-radius: 4px;
|
|
}
|
|
QLabel {
|
|
color: #e0e0e0;
|
|
}
|
|
""")
|
|
|
|
self.preview_name = QLabel("No attachment selected")
|
|
self.preview_name.setStyleSheet("font-weight: bold; color: #4caf50;")
|
|
self.preview_stats = QLabel("")
|
|
self.preview_stats.setStyleSheet("color: #888888;")
|
|
|
|
preview_layout.addRow(self.preview_name)
|
|
preview_layout.addRow(self.preview_stats)
|
|
layout.addWidget(self.preview_group)
|
|
|
|
# Buttons
|
|
button_layout = QHBoxLayout()
|
|
|
|
if self.allow_none:
|
|
self.none_btn = QPushButton("❌ Remove Attachment")
|
|
self.none_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #7d2e2e;
|
|
color: #e0e0e0;
|
|
border: 1px solid #f44336;
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #f44336;
|
|
}
|
|
""")
|
|
self.none_btn.clicked.connect(self._on_none)
|
|
button_layout.addWidget(self.none_btn)
|
|
|
|
button_layout.addStretch()
|
|
|
|
self.ok_btn = QPushButton("✓ Select")
|
|
self.ok_btn.setEnabled(False)
|
|
self.ok_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #2e7d32;
|
|
color: #e0e0e0;
|
|
border: 1px solid #4caf50;
|
|
border-radius: 4px;
|
|
padding: 8px 24px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #4caf50;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #3d3d3d;
|
|
border-color: #4d4d4d;
|
|
}
|
|
""")
|
|
self.ok_btn.clicked.connect(self._on_accept)
|
|
|
|
cancel_btn = QPushButton("Cancel")
|
|
cancel_btn.clicked.connect(self.reject)
|
|
|
|
button_layout.addWidget(self.ok_btn)
|
|
button_layout.addWidget(cancel_btn)
|
|
layout.addLayout(button_layout)
|
|
|
|
def _get_type_description(self) -> str:
|
|
"""Get description for attachment type."""
|
|
descriptions = {
|
|
'amplifier': 'Amplifiers increase weapon damage at the cost of additional decay and ammo.',
|
|
'scope': 'Scopes increase weapon range for better accuracy at distance.',
|
|
'absorber': 'Absorbers reduce incoming damage when attached to weapons.',
|
|
'plating': 'Armor plating adds protection values to your armor set.',
|
|
'finder_amp': 'Finder amplifiers increase mining depth and radius.',
|
|
'enhancer': 'Enhancers add special effects to gear.',
|
|
'implant': 'Mindforce implants boost mindforce chip performance.',
|
|
}
|
|
return descriptions.get(self.attachment_type, "")
|
|
|
|
def _load_attachments(self):
|
|
"""Load attachments from data source."""
|
|
attachments = get_mock_attachments(self.attachment_type)
|
|
self.attachments = attachments
|
|
|
|
self.list_widget.clear()
|
|
|
|
for att in attachments:
|
|
icon = self.TYPE_ICONS.get(self.attachment_type, '📎')
|
|
item = QListWidgetItem(f"{icon} {att.name}")
|
|
item.setData(Qt.ItemDataRole.UserRole, att)
|
|
|
|
# Build tooltip
|
|
tooltip_lines = [f"Decay: {att.decay_pec} PEC/shot"]
|
|
|
|
if isinstance(att, WeaponAmplifier):
|
|
tooltip_lines.append(f"Damage: +{att.damage_increase}")
|
|
tooltip_lines.append(f"Ammo: +{att.ammo_increase}")
|
|
elif isinstance(att, WeaponScope):
|
|
tooltip_lines.append(f"Range: +{att.range_increase}m")
|
|
if att.accuracy_bonus > 0:
|
|
tooltip_lines.append(f"Accuracy: +{att.accuracy_bonus}")
|
|
elif isinstance(att, WeaponAbsorber):
|
|
tooltip_lines.append(f"Damage Reduction: {att.damage_reduction}")
|
|
elif isinstance(att, ArmorPlating):
|
|
tooltip_lines.append(f"Total Protection: +{att.get_total_protection()}")
|
|
# Add individual protections
|
|
prots = []
|
|
if att.protection_impact > 0:
|
|
prots.append(f"Impact +{att.protection_impact}")
|
|
if att.protection_cut > 0:
|
|
prots.append(f"Cut +{att.protection_cut}")
|
|
if att.protection_stab > 0:
|
|
prots.append(f"Stab +{att.protection_stab}")
|
|
if att.protection_burn > 0:
|
|
prots.append(f"Burn +{att.protection_burn}")
|
|
if prots:
|
|
tooltip_lines.append(", ".join(prots))
|
|
elif isinstance(att, FinderAmplifier):
|
|
tooltip_lines.append(f"Depth: +{att.depth_increase}m")
|
|
tooltip_lines.append(f"Radius: +{att.radius_increase}m")
|
|
elif isinstance(att, Enhancer):
|
|
tooltip_lines.append(f"Tier: {att.tier}")
|
|
tooltip_lines.append(f"Effect: {att.effect_name} +{att.effect_value}")
|
|
elif isinstance(att, MindforceImplant):
|
|
tooltip_lines.append(f"Mindforce Bonus: +{att.mindforce_bonus}")
|
|
|
|
item.setToolTip("\n".join(tooltip_lines))
|
|
self.list_widget.addItem(item)
|
|
|
|
def _on_selection_changed(self):
|
|
"""Handle selection change."""
|
|
selected = self.list_widget.selectedItems()
|
|
if selected:
|
|
attachment = selected[0].data(Qt.ItemDataRole.UserRole)
|
|
self.selected_attachment = attachment
|
|
self.ok_btn.setEnabled(True)
|
|
self._update_preview(attachment)
|
|
else:
|
|
self.selected_attachment = None
|
|
self.ok_btn.setEnabled(False)
|
|
self.preview_name.setText("No attachment selected")
|
|
self.preview_stats.setText("")
|
|
|
|
def _update_preview(self, attachment):
|
|
"""Update preview panel."""
|
|
self.preview_name.setText(attachment.name)
|
|
|
|
stats_lines = []
|
|
stats_lines.append(f"<b>Decay:</b> {attachment.decay_pec} PEC/shot")
|
|
|
|
if isinstance(attachment, WeaponAmplifier):
|
|
stats_lines.append(f"<b>Damage Increase:</b> +{attachment.damage_increase}")
|
|
stats_lines.append(f"<b>Ammo Increase:</b> +{attachment.ammo_increase}")
|
|
|
|
# Calculate DPP impact
|
|
if attachment.damage_increase > 0:
|
|
# Rough DPP calculation for the amp
|
|
amp_cost = attachment.decay_pec + (Decimal(attachment.ammo_increase) * Decimal("0.01"))
|
|
amp_dpp = attachment.damage_increase / amp_cost if amp_cost > 0 else Decimal("0")
|
|
stats_lines.append(f"<b>Amp DPP:</b> {amp_dpp:.2f}")
|
|
|
|
elif isinstance(attachment, WeaponScope):
|
|
stats_lines.append(f"<b>Range Increase:</b> +{attachment.range_increase}m")
|
|
if attachment.accuracy_bonus > 0:
|
|
stats_lines.append(f"<b>Accuracy Bonus:</b> +{attachment.accuracy_bonus}")
|
|
|
|
elif isinstance(attachment, WeaponAbsorber):
|
|
stats_lines.append(f"<b>Damage Reduction:</b> -{attachment.damage_reduction}")
|
|
|
|
elif isinstance(attachment, ArmorPlating):
|
|
total = attachment.get_total_protection()
|
|
stats_lines.append(f"<b>Total Protection:</b> +{total}")
|
|
|
|
# Individual protections
|
|
prots = []
|
|
if attachment.protection_impact > 0:
|
|
prots.append(f"Impact +{attachment.protection_impact}")
|
|
if attachment.protection_cut > 0:
|
|
prots.append(f"Cut +{attachment.protection_cut}")
|
|
if attachment.protection_stab > 0:
|
|
prots.append(f"Stab +{attachment.protection_stab}")
|
|
if attachment.protection_penetration > 0:
|
|
prots.append(f"Pen +{attachment.protection_penetration}")
|
|
if attachment.protection_burn > 0:
|
|
prots.append(f"Burn +{attachment.protection_burn}")
|
|
if attachment.protection_cold > 0:
|
|
prots.append(f"Cold +{attachment.protection_cold}")
|
|
if attachment.protection_acid > 0:
|
|
prots.append(f"Acid +{attachment.protection_acid}")
|
|
if attachment.protection_electric > 0:
|
|
prots.append(f"Elec +{attachment.protection_electric}")
|
|
|
|
if prots:
|
|
stats_lines.append(f"<b>Protection:</b> {', '.join(prots)}")
|
|
|
|
elif isinstance(attachment, FinderAmplifier):
|
|
stats_lines.append(f"<b>Depth:</b> +{attachment.depth_increase}m")
|
|
stats_lines.append(f"<b>Radius:</b> +{attachment.radius_increase}m")
|
|
|
|
elif isinstance(attachment, Enhancer):
|
|
stats_lines.append(f"<b>Tier:</b> {attachment.tier}")
|
|
stats_lines.append(f"<b>Effect:</b> {attachment.effect_name} +{attachment.effect_value}")
|
|
|
|
elif isinstance(attachment, MindforceImplant):
|
|
stats_lines.append(f"<b>Mindforce Bonus:</b> +{attachment.mindforce_bonus}")
|
|
|
|
# Calculate cost per hour (assuming 3600 uses/hour)
|
|
cost_per_hour = (attachment.decay_pec * Decimal("3600")) / Decimal("100")
|
|
stats_lines.append(f"<b>Cost/Hour:</b> {cost_per_hour:.2f} PED")
|
|
|
|
self.preview_stats.setText("<br>".join(stats_lines))
|
|
|
|
def _on_double_click(self, item):
|
|
"""Handle double click."""
|
|
self._on_accept()
|
|
|
|
def _on_accept(self):
|
|
"""Handle OK button."""
|
|
if self.selected_attachment:
|
|
att = self.selected_attachment
|
|
|
|
# Build protection bonus dict for armor plating
|
|
protection_bonus = {}
|
|
if isinstance(att, ArmorPlating):
|
|
protection_bonus = {
|
|
'stab': att.protection_stab,
|
|
'cut': att.protection_cut,
|
|
'impact': att.protection_impact,
|
|
'penetration': att.protection_penetration,
|
|
'shrapnel': att.protection_shrapnel,
|
|
'burn': att.protection_burn,
|
|
'cold': att.protection_cold,
|
|
'acid': att.protection_acid,
|
|
'electric': att.protection_electric,
|
|
}
|
|
|
|
# Build extra data based on type
|
|
extra_data = {}
|
|
if isinstance(att, WeaponAmplifier):
|
|
extra_data['ammo_increase'] = att.ammo_increase
|
|
elif isinstance(att, Enhancer):
|
|
extra_data['tier'] = att.tier
|
|
extra_data['effect_name'] = att.effect_name
|
|
extra_data['effect_value'] = float(att.effect_value)
|
|
|
|
config = AttachmentConfig(
|
|
name=att.name,
|
|
item_id=att.item_id,
|
|
attachment_type=att.attachment_type,
|
|
decay_pec=att.decay_pec,
|
|
damage_bonus=getattr(att, 'damage_increase', Decimal("0")),
|
|
range_bonus=getattr(att, 'range_increase', Decimal("0")),
|
|
efficiency_bonus=Decimal("0"),
|
|
protection_bonus=protection_bonus,
|
|
extra_data=extra_data,
|
|
)
|
|
|
|
self.attachment_selected.emit(config)
|
|
self.accept()
|
|
|
|
def _on_none(self):
|
|
"""Remove attachment."""
|
|
self.attachment_selected.emit(None)
|
|
self.accept()
|
|
|
|
@staticmethod
|
|
def select_attachment(attachment_type: str, parent=None, allow_none: bool = True) -> Optional[AttachmentConfig]:
|
|
"""
|
|
Static method to open selector and return selected attachment.
|
|
|
|
Args:
|
|
attachment_type: Type of attachment to select
|
|
parent: Parent widget
|
|
allow_none: Whether to allow selecting "None"
|
|
|
|
Returns:
|
|
AttachmentConfig or None if cancelled or "None" selected
|
|
"""
|
|
dialog = AttachmentSelectorDialog(attachment_type, parent, allow_none)
|
|
result = None
|
|
|
|
def on_selected(att):
|
|
nonlocal result
|
|
result = att
|
|
|
|
dialog.attachment_selected.connect(on_selected)
|
|
|
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
return result
|
|
return None
|
|
|
|
|
|
# ============================================================================
|
|
# Multi-Attachment Manager
|
|
# ============================================================================
|
|
|
|
class AttachmentManagerWidget(QWidget):
|
|
"""
|
|
Widget for managing multiple attachments on a piece of gear.
|
|
Shows currently equipped attachments and allows adding/removing.
|
|
"""
|
|
|
|
attachment_changed = pyqtSignal(str, object) # slot_name, AttachmentConfig or None
|
|
|
|
def __init__(self, gear_type: str = 'weapon', parent=None):
|
|
super().__init__(parent)
|
|
self.gear_type = gear_type
|
|
self.attachments: Dict[str, Optional[AttachmentConfig]] = {}
|
|
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup the UI."""
|
|
from core.attachments import get_compatible_attachments
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(5)
|
|
|
|
# Get compatible attachment types
|
|
compatible_types = get_compatible_attachments(self.gear_type)
|
|
|
|
for att_type in compatible_types:
|
|
type_name = AttachmentSelectorDialog.TYPE_NAMES.get(att_type, att_type.title())
|
|
type_icon = AttachmentSelectorDialog.TYPE_ICONS.get(att_type, '📎')
|
|
|
|
row = QWidget()
|
|
row_layout = QHBoxLayout(row)
|
|
row_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
label = QLabel(f"{type_icon} {type_name}:")
|
|
label.setFixedWidth(120)
|
|
row_layout.addWidget(label)
|
|
|
|
status_label = QLabel("None")
|
|
status_label.setStyleSheet("color: #888888;")
|
|
row_layout.addWidget(status_label, stretch=1)
|
|
|
|
add_btn = QPushButton("+")
|
|
add_btn.setFixedSize(30, 24)
|
|
add_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #2e7d32;
|
|
color: white;
|
|
border-radius: 4px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #4caf50;
|
|
}
|
|
""")
|
|
add_btn.clicked.connect(lambda checked, t=att_type: self._on_add(t))
|
|
row_layout.addWidget(add_btn)
|
|
|
|
remove_btn = QPushButton("✕")
|
|
remove_btn.setFixedSize(30, 24)
|
|
remove_btn.setEnabled(False)
|
|
remove_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #7d2e2e;
|
|
color: white;
|
|
border-radius: 4px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #f44336;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #3d3d3d;
|
|
}
|
|
""")
|
|
remove_btn.clicked.connect(lambda checked, t=att_type: self._on_remove(t))
|
|
row_layout.addWidget(remove_btn)
|
|
|
|
layout.addWidget(row)
|
|
|
|
# Store references
|
|
self.attachments[att_type] = None
|
|
setattr(self, f"{att_type}_status", status_label)
|
|
setattr(self, f"{att_type}_add_btn", add_btn)
|
|
setattr(self, f"{att_type}_remove_btn", remove_btn)
|
|
|
|
def _on_add(self, attachment_type: str):
|
|
"""Open selector for attachment type."""
|
|
dialog = AttachmentSelectorDialog(attachment_type, self, allow_none=False)
|
|
|
|
def on_selected(att):
|
|
if att:
|
|
self.attachments[attachment_type] = att
|
|
self._update_display(attachment_type, att)
|
|
self.attachment_changed.emit(attachment_type, att)
|
|
|
|
dialog.attachment_selected.connect(on_selected)
|
|
dialog.exec()
|
|
|
|
def _on_remove(self, attachment_type: str):
|
|
"""Remove attachment."""
|
|
self.attachments[attachment_type] = None
|
|
self._update_display(attachment_type, None)
|
|
self.attachment_changed.emit(attachment_type, None)
|
|
|
|
def _update_display(self, attachment_type: str, att: Optional[AttachmentConfig]):
|
|
"""Update display for attachment type."""
|
|
status_label = getattr(self, f"{attachment_type}_status")
|
|
remove_btn = getattr(self, f"{attachment_type}_remove_btn")
|
|
|
|
if att:
|
|
# Build status text
|
|
status_parts = [att.name]
|
|
if att.damage_bonus > 0:
|
|
status_parts.append(f"(+{att.damage_bonus} dmg)")
|
|
elif att.range_bonus > 0:
|
|
status_parts.append(f"(+{att.range_bonus}m)")
|
|
|
|
status_label.setText(" ".join(status_parts))
|
|
status_label.setStyleSheet("color: #4caf50;")
|
|
remove_btn.setEnabled(True)
|
|
else:
|
|
status_label.setText("None")
|
|
status_label.setStyleSheet("color: #888888;")
|
|
remove_btn.setEnabled(False)
|
|
|
|
def get_attachment(self, attachment_type: str) -> Optional[AttachmentConfig]:
|
|
"""Get attachment of given type."""
|
|
return self.attachments.get(attachment_type)
|
|
|
|
def set_attachment(self, attachment_type: str, att: Optional[AttachmentConfig]):
|
|
"""Set attachment of given type."""
|
|
if attachment_type in self.attachments:
|
|
self.attachments[attachment_type] = att
|
|
self._update_display(attachment_type, att)
|
|
|
|
def get_all_attachments(self) -> Dict[str, Optional[AttachmentConfig]]:
|
|
"""Get all attachments."""
|
|
return self.attachments.copy()
|
|
|
|
|
|
# ============================================================================
|
|
# Main entry point for testing
|
|
# ============================================================================
|
|
|
|
def main():
|
|
"""Test the attachment selector."""
|
|
import sys
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
app = QApplication(sys.argv)
|
|
app.setStyle('Fusion')
|
|
|
|
# Test single selector
|
|
print("Testing single attachment selector...")
|
|
result = AttachmentSelectorDialog.select_attachment('amplifier')
|
|
|
|
if result:
|
|
print(f"Selected: {result.name}")
|
|
print(f" Decay: {result.decay_pec} PEC")
|
|
print(f" Damage Bonus: {result.damage_bonus}")
|
|
else:
|
|
print("No attachment selected")
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|