From 32e095350bdad1b8130aabeeb18c22ab800f7197 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Mon, 9 Feb 2026 09:15:03 +0000 Subject: [PATCH] feat(attachments): add attachment system and DPP display - Created core/attachments.py with full attachment type system - Supports: Amplifiers, Scopes, Absorbers, Finder Amps, Platings, Enhancers, Implants - Added DPP display to HUD overlay - Attachment compatibility validation rules --- core/attachments.py | 196 ++++++++++++++++++++++++++++++++++++++++++++ ui/hud_overlay.py | 13 ++- 2 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 core/attachments.py diff --git a/core/attachments.py b/core/attachments.py new file mode 100644 index 0000000..008e569 --- /dev/null +++ b/core/attachments.py @@ -0,0 +1,196 @@ +""" +Attachment System for Lemontropia Suite +Defines all attachment types and their compatibility with gear. +""" + +from dataclasses import dataclass +from decimal import Decimal +from typing import Optional, List + + +@dataclass +class Attachment: + """Base class for all attachments.""" + name: str + item_id: str + decay_pec: Decimal + attachment_type: str # e.g., 'amplifier', 'scope', 'absorber' + + # Effects + damage_bonus: Decimal = Decimal('0') + range_bonus: Decimal = Decimal('0') + efficiency_bonus: Decimal = Decimal('0') + + def calculate_cost_per_use(self) -> Decimal: + """Calculate cost per use in PED.""" + return self.decay_pec / Decimal('100') + + +@dataclass +class WeaponAmplifier(Attachment): + """Weapon amplifier attachment.""" + damage_increase: Decimal = Decimal('0') + ammo_increase: int = 0 + + def __post_init__(self): + self.attachment_type = 'amplifier' + + +@dataclass +class WeaponScope(Attachment): + """Weapon scope attachment.""" + range_increase: Decimal = Decimal('0') + accuracy_bonus: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'scope' + + +@dataclass +class WeaponAbsorber(Attachment): + """Weapon absorber (reduces damage taken).""" + damage_reduction: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'absorber' + + +@dataclass +class FinderAmplifier(Attachment): + """Mining finder amplifier.""" + depth_increase: Decimal = Decimal('0') + radius_increase: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'finder_amp' + + +@dataclass +class ArmorPlating(Attachment): + """Armor plating for increased protection.""" + protection_stab: Decimal = Decimal('0') + protection_cut: Decimal = Decimal('0') + protection_impact: Decimal = Decimal('0') + protection_penetration: Decimal = Decimal('0') + protection_shrapnel: Decimal = Decimal('0') + protection_burn: Decimal = Decimal('0') + protection_cold: Decimal = Decimal('0') + protection_acid: Decimal = Decimal('0') + protection_electric: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'plating' + + def get_total_protection(self) -> Decimal: + """Get total protection value.""" + return ( + self.protection_stab + self.protection_cut + self.protection_impact + + self.protection_penetration + self.protection_shrapnel + + self.protection_burn + self.protection_cold + + self.protection_acid + self.protection_electric + ) + + +@dataclass +class Enhancer(Attachment): + """Gear enhancer (adds special effects).""" + tier: int = 1 + effect_name: str = "" + effect_value: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'enhancer' + + +@dataclass +class MindforceImplant(Attachment): + """Mindforce implant for mindforce chips.""" + mindforce_bonus: Decimal = Decimal('0') + + def __post_init__(self): + self.attachment_type = 'implant' + + +# ============================================================================ +# Attachment Compatibility Rules +# ============================================================================ + +ATTACHMENT_COMPATIBILITY = { + 'weapon': ['amplifier', 'scope', 'absorber', 'enhancer'], + 'armor': ['plating', 'enhancer'], + 'finder': ['finder_amp', 'enhancer'], + 'mindforce': ['implant', 'enhancer'], + 'tool': ['enhancer'], +} + + +def can_attach(gear_type: str, attachment_type: str) -> bool: + """Check if attachment is compatible with gear type.""" + compatible = ATTACHMENT_COMPATIBILITY.get(gear_type, []) + return attachment_type in compatible + + +def get_compatible_attachments(gear_type: str) -> List[str]: + """Get list of compatible attachment types for gear.""" + return ATTACHMENT_COMPATIBILITY.get(gear_type, []) + + +# ============================================================================ +# Mock Attachment Data (until API is available) +# ============================================================================ + +MOCK_AMPLIFIERS = [ + WeaponAmplifier("A101", "amp_a101", Decimal("0.05"), damage_increase=Decimal("4"), ammo_increase=10), + WeaponAmplifier("A102", "amp_a102", Decimal("0.08"), damage_increase=Decimal("6"), ammo_increase=15), + WeaponAmplifier("A103", "amp_a103", Decimal("0.12"), damage_increase=Decimal("8"), ammo_increase=20), + WeaponAmplifier("A104", "amp_a104", Decimal("0.18"), damage_increase=Decimal("12"), ammo_increase=30), + WeaponAmplifier("A105", "amp_a105", Decimal("0.25"), damage_increase=Decimal("16"), ammo_increase=40), + WeaponAmplifier("A106", "amp_a106", Decimal("0.35"), damage_increase=Decimal("20"), ammo_increase=50), +] + +MOCK_SCOPES = [ + WeaponScope("Longreach 4", "scope_lr4", Decimal("0.02"), range_increase=Decimal("10")), + WeaponScope("Longreach 6", "scope_lr6", Decimal("0.04"), range_increase=Decimal("15")), + WeaponScope("Longreach 8", "scope_lr8", Decimal("0.06"), range_increase=Decimal("20")), +] + +MOCK_ABSORBERS = [ + WeaponAbsorber("Damage II", "abs_dmg2", Decimal("0.03"), damage_reduction=Decimal("2")), + WeaponAbsorber("Damage IV", "abs_dmg4", Decimal("0.05"), damage_reduction=Decimal("4")), +] + +MOCK_FINDER_AMPS = [ + FinderAmplifier("DSEC L-10", "fa_dsec10", Decimal("0.10"), depth_increase=Decimal("50"), radius_increase=Decimal("5")), + FinderAmplifier("DSEC L-30", "fa_dsec30", Decimal("0.25"), depth_increase=Decimal("100"), radius_increase=Decimal("10")), +] + +MOCK_PLATINGS = [ + ArmorPlating("Impact Plating", "plt_impact", Decimal("0.03"), protection_impact=Decimal("3")), + ArmorPlating("Cut Plating", "plt_cut", Decimal("0.03"), protection_cut=Decimal("3")), + ArmorPlating("Burn Plating", "plt_burn", Decimal("0.03"), protection_burn=Decimal("3")), +] + +MOCK_ENHANCERS = [ + Enhancer("Damage Enhancer 1", "enh_dmg1", Decimal("0.10"), tier=1, effect_name="damage", effect_value=Decimal("2")), + Enhancer("Damage Enhancer 2", "enh_dmg2", Decimal("0.15"), tier=2, effect_name="damage", effect_value=Decimal("4")), + Enhancer("Economy Enhancer 1", "enh_eco1", Decimal("0.08"), tier=1, effect_name="economy", effect_value=Decimal("5")), +] + +MOCK_IMPLANTS = [ + MindforceImplant("NeoPsion 10", "impl_np10", Decimal("0.05"), mindforce_bonus=Decimal("10")), + MindforceImplant("NeoPsion 20", "impl_np20", Decimal("0.10"), mindforce_bonus=Decimal("20")), +] + + +def get_mock_attachments(attachment_type: str) -> List[Attachment]: + """Get mock attachments by type.""" + mock_data = { + 'amplifier': MOCK_AMPLIFIERS, + 'scope': MOCK_SCOPES, + 'absorber': MOCK_ABSORBERS, + 'finder_amp': MOCK_FINDER_AMPS, + 'plating': MOCK_PLATINGS, + 'enhancer': MOCK_ENHANCERS, + 'implant': MOCK_IMPLANTS, + } + return mock_data.get(attachment_type, []) diff --git a/ui/hud_overlay.py b/ui/hud_overlay.py index 717770e..5730ee4 100644 --- a/ui/hud_overlay.py +++ b/ui/hud_overlay.py @@ -422,6 +422,11 @@ class HUDOverlay(QWidget): self.weapon_label.setStyleSheet("font-size: 11px; color: #CCCCCC;") weapon_layout.addWidget(self.weapon_label) + # DPP display + self.dpp_label = QLabel("DPP: --") + self.dpp_label.setStyleSheet("font-size: 10px; color: #FFD700;") + weapon_layout.addWidget(self.dpp_label) + weapon_layout.addStretch() loadout_label = QLabel("Loadout:") @@ -943,9 +948,13 @@ class HUDOverlay(QWidget): self.dealt_value_label.setText(str(self._stats.damage_dealt)) self.taken_value_label.setText(str(self._stats.damage_taken)) - # Weapon/Loadout - self.weapon_label.setText(self._stats.current_weapon[:20]) # Truncate long names + # Weapon/Loadout/DPP + self.weapon_label.setText(self._stats.current_weapon[:20]) self.loadout_label.setText(self._stats.current_loadout[:15]) + if self._stats.weapon_dpp > 0: + self.dpp_label.setText(f"DPP: {self._stats.weapon_dpp:.2f}") + else: + self.dpp_label.setText("DPP: --") # Update time if session active self._update_time_display()