diff --git a/ui/HUD_REDESIGN.md b/ui/HUD_REDESIGN.md
new file mode 100644
index 0000000..b5cf728
--- /dev/null
+++ b/ui/HUD_REDESIGN.md
@@ -0,0 +1,125 @@
+# HUD Redesign - Clean & Customizable
+
+## New Features
+
+### 1. **Simplified Default View**
+By default, the new HUD shows only the essentials:
+- Status indicator (● Live)
+- Current gear (weapon, armor, loadout)
+- **Profit/Loss** (big, prominent)
+- **Return %** (big, prominent)
+- Cost per shot/hit/heal
+- Session time
+- Drag hint
+
+### 2. **Customizable Display**
+Click the **⚙️ (settings)** button to choose what to show:
+- ✅ Core Stats (recommended on)
+ - Session Time
+ - Profit/Loss
+ - Return %
+ - Cost per Shot/Hit/Heal
+ - Current Gear
+- ⬜ Optional Stats (off by default)
+ - Cost Breakdown (weapon/armor/heal separate costs)
+ - Combat Stats (kills, globals)
+ - Damage Stats (dealt/taken)
+ - Shrapnel Amount
+- 📏 Display Mode
+ - Compact Mode (smaller font, narrower)
+
+### 3. **Auto-Size Based on Content**
+The HUD automatically adjusts its height based on what you're showing. No wasted space!
+
+## Files
+
+| File | Purpose |
+|------|---------|
+| `ui/hud_overlay_clean.py` | New clean HUD implementation |
+
+## How to Test
+
+Replace the import in `main_window.py`:
+
+```python
+# OLD:
+from ui.hud_overlay import HUDOverlay
+
+# NEW:
+from ui.hud_overlay_clean import HUDOverlay
+```
+
+## Default Layout (Clean)
+
+```
+┌─────────────────────────┐
+│ ● Ready [⚙️] │
+├─────────────────────────┤
+│ 🔫 ArMatrix BP-25 │
+│ 🛡️ Frontier, Adjusted │
+│ 📋 Test Loadout │
+├─────────────────────────┤
+│ P/L: +12.50 PED 105% │
+├─────────────────────────┤
+│ Shot: 0.091 Hit: 0.0001│
+├─────────────────────────┤
+│ 00:23:45 Ctrl+drag │
+└─────────────────────────┘
+```
+
+## Expanded Layout (All Stats)
+
+```
+┌─────────────────────────┐
+│ ● Live [⚙️] │
+├─────────────────────────┤
+│ 🔫 ArMatrix BP-25 │
+│ 🛡️ Frontier, Adjusted │
+│ 📋 Test Loadout │
+├─────────────────────────┤
+│ P/L: +12.50 PED 105% │
+├─────────────────────────┤
+│ Shot: 0.091 Hit: 0.0001│
+├─────────────────────────┤
+│ Weapon: 2.15 │
+│ Armor: 0.50 │
+│ Healing: 0.25 │
+├─────────────────────────┤
+│ Kills: 15 Globals: 2 │
+├─────────────────────────┤
+│ 00:23:45 Ctrl+drag │
+└─────────────────────────┘
+```
+
+## Migration
+
+To migrate to the new HUD:
+
+1. Backup your current `hud_overlay.py`
+2. Replace import in `main_window.py`
+3. Test the new layout
+4. Use ⚙️ button to customize
+5. Settings are saved automatically
+
+## Settings File
+
+Settings are saved to:
+```
+%USERPROFILE%\.lemontropia\hud_config.json
+```
+
+Example:
+```json
+{
+ "show_session_time": true,
+ "show_profit_loss": true,
+ "show_return_pct": true,
+ "show_cost_breakdown": false,
+ "show_combat_stats": false,
+ "show_damage_stats": false,
+ "show_cost_metrics": true,
+ "show_shrapnel": false,
+ "show_gear_info": true,
+ "compact_mode": false
+}
+```
diff --git a/ui/hud_overlay_clean.py b/ui/hud_overlay_clean.py
new file mode 100644
index 0000000..e87939f
--- /dev/null
+++ b/ui/hud_overlay_clean.py
@@ -0,0 +1,722 @@
+"""
+Lemontropia Suite - HUD Overlay v2.0
+Cleaner, customizable HUD with collapsible sections.
+"""
+
+import sys
+import json
+from pathlib import Path
+from decimal import Decimal
+from datetime import datetime, timedelta
+from dataclasses import dataclass, asdict, field
+from typing import Optional, Dict, Any, List, Set
+
+# Windows-specific imports for click-through support
+if sys.platform == 'win32':
+ import ctypes
+ from ctypes import wintypes
+
+ user32 = ctypes.windll.user32
+ GetForegroundWindow = user32.GetForegroundWindow
+ GetForegroundWindow.restype = wintypes.HWND
+
+from PyQt6.QtWidgets import (
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout,
+ QLabel, QFrame, QSizePolicy, QPushButton, QMenu,
+ QDialog, QCheckBox, QFormLayout, QScrollArea
+)
+from PyQt6.QtCore import (
+ Qt, QTimer, pyqtSignal, QPoint, QSettings,
+ QObject
+)
+from PyQt6.QtGui import QFont, QColor, QPalette, QMouseEvent
+
+
+@dataclass
+class HUDConfig:
+ """Configuration for which HUD elements to show."""
+ show_session_time: bool = True
+ show_profit_loss: bool = True
+ show_return_pct: bool = True
+ show_cost_breakdown: bool = False # Weapon/Armor/Heal costs
+ show_combat_stats: bool = False # Kills, Globals, DPP
+ show_damage_stats: bool = False # Damage dealt/taken
+ show_cost_metrics: bool = True # Cost per shot/hit/heal
+ show_shrapnel: bool = False
+ show_gear_info: bool = True
+
+ compact_mode: bool = False
+
+ def to_dict(self) -> dict:
+ return {
+ 'show_session_time': self.show_session_time,
+ 'show_profit_loss': self.show_profit_loss,
+ 'show_return_pct': self.show_return_pct,
+ 'show_cost_breakdown': self.show_cost_breakdown,
+ 'show_combat_stats': self.show_combat_stats,
+ 'show_damage_stats': self.show_damage_stats,
+ 'show_cost_metrics': self.show_cost_metrics,
+ 'show_shrapnel': self.show_shrapnel,
+ 'show_gear_info': self.show_gear_info,
+ 'compact_mode': self.compact_mode,
+ }
+
+ @classmethod
+ def from_dict(cls, data: dict) -> "HUDConfig":
+ return cls(
+ show_session_time=data.get('show_session_time', True),
+ show_profit_loss=data.get('show_profit_loss', True),
+ show_return_pct=data.get('show_return_pct', True),
+ show_cost_breakdown=data.get('show_cost_breakdown', False),
+ show_combat_stats=data.get('show_combat_stats', False),
+ show_damage_stats=data.get('show_damage_stats', False),
+ show_cost_metrics=data.get('show_cost_metrics', True),
+ show_shrapnel=data.get('show_shrapnel', False),
+ show_gear_info=data.get('show_gear_info', True),
+ compact_mode=data.get('compact_mode', False),
+ )
+
+
+@dataclass
+class HUDStats:
+ """Simplified stats for HUD display."""
+ session_time: timedelta = field(default_factory=lambda: timedelta(0))
+
+ # Financial (core)
+ loot_total: Decimal = Decimal('0.0')
+ cost_total: Decimal = Decimal('0.0')
+ profit_loss: Decimal = Decimal('0.0')
+ return_percentage: Decimal = Decimal('0.0')
+
+ # Cost breakdown (optional)
+ weapon_cost_total: Decimal = Decimal('0.0')
+ armor_cost_total: Decimal = Decimal('0.0')
+ healing_cost_total: Decimal = Decimal('0.0')
+
+ # Combat (optional)
+ kills: int = 0
+ globals_count: int = 0
+ hofs_count: int = 0
+
+ # Cost metrics (core)
+ cost_per_shot: Decimal = Decimal('0.0')
+ cost_per_hit: Decimal = Decimal('0.0')
+ cost_per_heal: Decimal = Decimal('0.0')
+
+ # Gear
+ current_weapon: str = "None"
+ current_armor: str = "None"
+ current_fap: str = "None"
+ current_loadout: str = "None"
+
+ def recalculate(self):
+ """Recalculate derived values."""
+ self.profit_loss = self.loot_total - self.cost_total
+ if self.cost_total > 0:
+ self.return_percentage = (self.loot_total / self.cost_total) * Decimal('100')
+ else:
+ self.return_percentage = Decimal('0')
+
+ def to_dict(self) -> dict:
+ return {
+ 'session_time': str(self.session_time),
+ 'loot_total': str(self.loot_total),
+ 'cost_total': str(self.cost_total),
+ 'profit_loss': str(self.profit_loss),
+ 'return_percentage': str(self.return_percentage),
+ 'weapon_cost_total': str(self.weapon_cost_total),
+ 'armor_cost_total': str(self.armor_cost_total),
+ 'healing_cost_total': str(self.healing_cost_total),
+ 'kills': self.kills,
+ 'globals_count': self.globals_count,
+ 'hofs_count': self.hofs_count,
+ 'cost_per_shot': str(self.cost_per_shot),
+ 'cost_per_hit': str(self.cost_per_hit),
+ 'cost_per_heal': str(self.cost_per_heal),
+ 'current_weapon': self.current_weapon,
+ 'current_armor': self.current_armor,
+ 'current_fap': self.current_fap,
+ 'current_loadout': self.current_loadout,
+ }
+
+
+class HUDSettingsDialog(QDialog):
+ """Dialog to configure which HUD elements to show."""
+
+ def __init__(self, config: HUDConfig, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("HUD Settings")
+ self.setMinimumWidth(300)
+
+ self.config = config
+ self._setup_ui()
+
+ def _setup_ui(self):
+ layout = QVBoxLayout(self)
+
+ scroll = QScrollArea()
+ scroll.setWidgetResizable(True)
+ content = QWidget()
+ form = QFormLayout(content)
+
+ # Core stats (always recommended)
+ form.addRow(QLabel("Core Stats (Recommended)"))
+
+ self.cb_time = QCheckBox("Session Time")
+ self.cb_time.setChecked(self.config.show_session_time)
+ form.addRow(self.cb_time)
+
+ self.cb_profit = QCheckBox("Profit/Loss")
+ self.cb_profit.setChecked(self.config.show_profit_loss)
+ form.addRow(self.cb_profit)
+
+ self.cb_return = QCheckBox("Return %")
+ self.cb_return.setChecked(self.config.show_return_pct)
+ form.addRow(self.cb_return)
+
+ self.cb_cost_metrics = QCheckBox("Cost per Shot/Hit/Heal")
+ self.cb_cost_metrics.setChecked(self.config.show_cost_metrics)
+ form.addRow(self.cb_cost_metrics)
+
+ self.cb_gear = QCheckBox("Current Gear")
+ self.cb_gear.setChecked(self.config.show_gear_info)
+ form.addRow(self.cb_gear)
+
+ # Optional stats
+ form.addRow(QLabel("Optional Stats"))
+
+ self.cb_cost_breakdown = QCheckBox("Cost Breakdown (Weapon/Armor/Heal)")
+ self.cb_cost_breakdown.setChecked(self.config.show_cost_breakdown)
+ form.addRow(self.cb_cost_breakdown)
+
+ self.cb_combat = QCheckBox("Combat Stats (Kills, Globals)")
+ self.cb_combat.setChecked(self.config.show_combat_stats)
+ form.addRow(self.cb_combat)
+
+ self.cb_damage = QCheckBox("Damage Stats (Dealt/Taken)")
+ self.cb_damage.setChecked(self.config.show_damage_stats)
+ form.addRow(self.cb_damage)
+
+ self.cb_shrapnel = QCheckBox("Shrapnel Amount")
+ self.cb_shrapnel.setChecked(self.config.show_shrapnel)
+ form.addRow(self.cb_shrapnel)
+
+ # Display mode
+ form.addRow(QLabel("Display Mode"))
+
+ self.cb_compact = QCheckBox("Compact Mode (smaller font)")
+ self.cb_compact.setChecked(self.config.compact_mode)
+ form.addRow(self.cb_compact)
+
+ scroll.setWidget(content)
+ layout.addWidget(scroll)
+
+ # Buttons
+ btn_layout = QHBoxLayout()
+ btn_layout.addStretch()
+
+ save_btn = QPushButton("Save")
+ save_btn.clicked.connect(self._on_save)
+ btn_layout.addWidget(save_btn)
+
+ cancel_btn = QPushButton("Cancel")
+ cancel_btn.clicked.connect(self.reject)
+ btn_layout.addWidget(cancel_btn)
+
+ layout.addLayout(btn_layout)
+
+ def _on_save(self):
+ self.config.show_session_time = self.cb_time.isChecked()
+ self.config.show_profit_loss = self.cb_profit.isChecked()
+ self.config.show_return_pct = self.cb_return.isChecked()
+ self.config.show_cost_metrics = self.cb_cost_metrics.isChecked()
+ self.config.show_gear_info = self.cb_gear.isChecked()
+ self.config.show_cost_breakdown = self.cb_cost_breakdown.isChecked()
+ self.config.show_combat_stats = self.cb_combat.isChecked()
+ self.config.show_damage_stats = self.cb_damage.isChecked()
+ self.config.show_shrapnel = self.cb_shrapnel.isChecked()
+ self.config.compact_mode = self.cb_compact.isChecked()
+ self.accept()
+
+ def get_config(self) -> HUDConfig:
+ return self.config
+
+
+class HUDOverlay(QWidget):
+ """
+ Clean, customizable HUD Overlay for Lemontropia Suite.
+
+ Features:
+ - Collapsible sections
+ - Customizable display elements
+ - Compact mode
+ - Draggable with Ctrl+click
+ """
+
+ position_changed = pyqtSignal(QPoint)
+ stats_updated = pyqtSignal(dict)
+
+ def __init__(self, parent=None, config_path: Optional[str] = None):
+ super().__init__(parent)
+
+ self.config_path = Path(config_path) if config_path else Path.home() / ".lemontropia" / "hud_config.json"
+ self.config_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # Load HUD config
+ self.hud_config = self._load_hud_config()
+
+ # Session state
+ self._session_start: Optional[datetime] = None
+ self._stats = HUDStats()
+ self.session_active = False
+
+ # Drag state
+ self._dragging = False
+ self._drag_offset = QPoint()
+
+ # Setup
+ self._setup_window()
+ self._setup_ui()
+ self._load_position()
+
+ # Timer for session time
+ self._timer = QTimer(self)
+ self._timer.timeout.connect(self._update_session_time)
+
+ def _load_hud_config(self) -> HUDConfig:
+ """Load HUD configuration."""
+ try:
+ if self.config_path.exists():
+ with open(self.config_path, 'r') as f:
+ data = json.load(f)
+ return HUDConfig.from_dict(data)
+ except Exception as e:
+ pass
+ return HUDConfig() # Default config
+
+ def _save_hud_config(self):
+ """Save HUD configuration."""
+ try:
+ with open(self.config_path, 'w') as f:
+ json.dump(self.hud_config.to_dict(), f, indent=2)
+ except Exception as e:
+ pass
+
+ def _setup_window(self):
+ """Configure window properties."""
+ self.setWindowFlags(
+ Qt.WindowType.FramelessWindowHint |
+ Qt.WindowType.WindowStaysOnTopHint |
+ Qt.WindowType.Tool
+ )
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
+ self.setMouseTracking(True)
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+
+ def _setup_ui(self):
+ """Build the HUD UI."""
+ # Calculate size based on what's shown
+ self._update_window_size()
+
+ # Main container
+ self.container = QFrame(self)
+ self.container.setObjectName("hudContainer")
+ self.container.setStyleSheet(self._get_stylesheet())
+
+ layout = QVBoxLayout(self.container)
+ layout.setContentsMargins(12, 12, 12, 8)
+ layout.setSpacing(4)
+
+ # === HEADER: Status + Gear ===
+ header = QHBoxLayout()
+
+ self.status_label = QLabel("● Ready")
+ self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;")
+ header.addWidget(self.status_label)
+
+ header.addStretch()
+
+ # Settings button
+ self.settings_btn = QPushButton("⚙️")
+ self.settings_btn.setFixedSize(24, 24)
+ self.settings_btn.setStyleSheet("""
+ QPushButton {
+ background: transparent;
+ border: none;
+ font-size: 14px;
+ }
+ QPushButton:hover {
+ background: rgba(255, 255, 255, 30);
+ border-radius: 4px;
+ }
+ """)
+ self.settings_btn.clicked.connect(self._show_settings)
+ header.addWidget(self.settings_btn)
+
+ layout.addLayout(header)
+
+ # === GEAR INFO (if enabled) ===
+ if self.hud_config.show_gear_info:
+ self.gear_frame = QFrame()
+ gear_layout = QVBoxLayout(self.gear_frame)
+ gear_layout.setContentsMargins(0, 0, 0, 0)
+ gear_layout.setSpacing(2)
+
+ self.weapon_label = QLabel("🔫 None")
+ self.armor_label = QLabel("🛡️ None")
+ self.loadout_label = QLabel("📋 No Loadout")
+
+ gear_layout.addWidget(self.weapon_label)
+ gear_layout.addWidget(self.armor_label)
+ gear_layout.addWidget(self.loadout_label)
+
+ layout.addWidget(self.gear_frame)
+
+ # Separator
+ sep = QFrame()
+ sep.setFrameShape(QFrame.Shape.HLine)
+ sep.setStyleSheet("background-color: rgba(255, 215, 0, 50);")
+ sep.setFixedHeight(1)
+ layout.addWidget(sep)
+
+ # === CORE: P/L + Return % ===
+ if self.hud_config.show_profit_loss or self.hud_config.show_return_pct:
+ core_frame = QFrame()
+ core_layout = QHBoxLayout(core_frame)
+ core_layout.setContentsMargins(0, 4, 0, 4)
+
+ if self.hud_config.show_profit_loss:
+ self.profit_label = QLabel("P/L: 0.00 PED")
+ self.profit_label.setStyleSheet("font-size: 18px; font-weight: bold;")
+ core_layout.addWidget(self.profit_label)
+
+ core_layout.addStretch()
+
+ if self.hud_config.show_return_pct:
+ self.return_label = QLabel("0.0%")
+ self.return_label.setStyleSheet("font-size: 16px; font-weight: bold;")
+ core_layout.addWidget(self.return_label)
+
+ layout.addWidget(core_frame)
+
+ # === COST METRICS (if enabled) ===
+ if self.hud_config.show_cost_metrics:
+ metrics_frame = QFrame()
+ metrics_frame.setStyleSheet("background-color: rgba(0, 0, 0, 100); border-radius: 4px;")
+ metrics_layout = QHBoxLayout(metrics_frame)
+ metrics_layout.setContentsMargins(8, 4, 8, 4)
+
+ self.cps_label = QLabel("Shot: 0.0000")
+ self.cph_label = QLabel("Hit: 0.0000")
+ self.cphl_label = QLabel("Heal: 0.0000")
+
+ for lbl in [self.cps_label, self.cph_label, self.cphl_label]:
+ lbl.setStyleSheet("font-size: 10px; color: #AAAAAA;")
+ metrics_layout.addWidget(lbl)
+
+ metrics_layout.addStretch()
+ layout.addWidget(metrics_frame)
+
+ # === OPTIONAL: Cost Breakdown ===
+ if self.hud_config.show_cost_breakdown:
+ breakdown_frame = QFrame()
+ breakdown_layout = QFormLayout(breakdown_frame)
+ breakdown_layout.setContentsMargins(0, 0, 0, 0)
+ breakdown_layout.setSpacing(2)
+
+ self.wep_cost_label = QLabel("0.00")
+ self.arm_cost_label = QLabel("0.00")
+ self.heal_cost_label = QLabel("0.00")
+
+ breakdown_layout.addRow("Weapon:", self.wep_cost_label)
+ breakdown_layout.addRow("Armor:", self.arm_cost_label)
+ breakdown_layout.addRow("Healing:", self.heal_cost_label)
+
+ layout.addWidget(breakdown_frame)
+
+ # === OPTIONAL: Combat Stats ===
+ if self.hud_config.show_combat_stats:
+ combat_frame = QFrame()
+ combat_layout = QHBoxLayout(combat_frame)
+ combat_layout.setContentsMargins(0, 0, 0, 0)
+
+ self.kills_label = QLabel("Kills: 0")
+ self.globals_label = QLabel("Globals: 0")
+
+ combat_layout.addWidget(self.kills_label)
+ combat_layout.addStretch()
+ combat_layout.addWidget(self.globals_label)
+
+ layout.addWidget(combat_frame)
+
+ # === FOOTER: Session Time + Drag Hint ===
+ footer = QHBoxLayout()
+
+ if self.hud_config.show_session_time:
+ self.time_label = QLabel("00:00:00")
+ self.time_label.setStyleSheet("color: #888; font-size: 11px;")
+ footer.addWidget(self.time_label)
+ else:
+ footer.addStretch()
+
+ footer.addStretch()
+
+ self.drag_hint = QLabel("Ctrl+drag to move")
+ self.drag_hint.setStyleSheet("font-size: 9px; color: #666;")
+ footer.addWidget(self.drag_hint)
+
+ layout.addLayout(footer)
+
+ def _get_stylesheet(self) -> str:
+ """Get stylesheet based on compact mode."""
+ font_size = "11px" if self.hud_config.compact_mode else "12px"
+
+ return f"""
+ #hudContainer {{
+ background-color: rgba(0, 0, 0, 180);
+ border: 1px solid rgba(255, 215, 0, 80);
+ border-radius: 8px;
+ }}
+ QLabel {{
+ color: #FFFFFF;
+ font-family: 'Segoe UI', 'Arial', sans-serif;
+ font-size: {font_size};
+ }}
+ """
+
+ def _update_window_size(self):
+ """Calculate window size based on enabled features."""
+ height = 80 # Base height
+
+ if self.hud_config.show_gear_info:
+ height += 70
+ if self.hud_config.show_profit_loss or self.hud_config.show_return_pct:
+ height += 40
+ if self.hud_config.show_cost_metrics:
+ height += 30
+ if self.hud_config.show_cost_breakdown:
+ height += 70
+ if self.hud_config.show_combat_stats:
+ height += 25
+ if self.hud_config.show_session_time:
+ height += 20
+
+ width = 280 if self.hud_config.compact_mode else 320
+
+ self.setFixedSize(width, height)
+ self.container.setFixedSize(width, height)
+
+ def _show_settings(self):
+ """Show settings dialog."""
+ dialog = HUDSettingsDialog(self.hud_config, self)
+ if dialog.exec() == QDialog.DialogCode.Accepted:
+ self.hud_config = dialog.get_config()
+ self._save_hud_config()
+ # Rebuild UI with new settings
+ self._rebuild_ui()
+
+ def _rebuild_ui(self):
+ """Rebuild UI with current config."""
+ # Clear container
+ while self.container.layout().count():
+ item = self.container.layout().takeAt(0)
+ if item.widget():
+ item.widget().deleteLater()
+
+ self._update_window_size()
+ self._setup_ui()
+ self._refresh_display()
+
+ # === Session Management ===
+
+ def start_session(self, weapon: str = "Unknown", armor: str = "None",
+ fap: str = "None", loadout: str = "Default",
+ cost_per_shot: Decimal = Decimal('0.0'),
+ cost_per_hit: Decimal = Decimal('0.0'),
+ cost_per_heal: Decimal = Decimal('0.0')) -> None:
+ """Start a new hunting session."""
+ self._session_start = datetime.now()
+ self._stats = HUDStats()
+ self._stats.current_weapon = weapon
+ self._stats.current_armor = armor
+ self._stats.current_fap = fap
+ self._stats.current_loadout = loadout
+ self._stats.cost_per_shot = cost_per_shot
+ self._stats.cost_per_hit = cost_per_hit
+ self._stats.cost_per_heal = cost_per_heal
+ self.session_active = True
+
+ self._timer.start(1000)
+ self._refresh_display()
+ self.status_label.setText("● Live")
+ self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;")
+
+ def stop_session(self) -> None:
+ """Stop the current session."""
+ self.session_active = False
+ self._timer.stop()
+ self.status_label.setText("● Stopped")
+ self.status_label.setStyleSheet("color: #FF7F7F; font-weight: bold;")
+
+ # === Event Handlers ===
+
+ def _update_session_time(self):
+ """Update session time display."""
+ if self._session_start and self.session_active:
+ elapsed = datetime.now() - self._session_start
+ hours, remainder = divmod(elapsed.seconds, 3600)
+ minutes, seconds = divmod(remainder, 60)
+
+ if hasattr(self, 'time_label'):
+ self.time_label.setText(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
+
+ def _refresh_display(self):
+ """Refresh all display labels."""
+ # Profit/Loss
+ if hasattr(self, 'profit_label'):
+ profit = self._stats.profit_loss
+ color = "#7FFF7F" if profit >= 0 else "#FF7F7F"
+ self.profit_label.setText(f"{profit:+.2f} PED")
+ self.profit_label.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {color};")
+
+ # Return %
+ if hasattr(self, 'return_label'):
+ ret = self._stats.return_percentage
+ if ret >= 100:
+ color = "#7FFF7F"
+ elif ret >= 90:
+ color = "#FFFF7F"
+ else:
+ color = "#FF7F7F"
+ self.return_label.setText(f"{ret:.1f}%")
+ self.return_label.setStyleSheet(f"font-size: 16px; font-weight: bold; color: {color};")
+
+ # Cost metrics
+ if hasattr(self, 'cps_label'):
+ self.cps_label.setText(f"Shot: {self._stats.cost_per_shot:.4f}")
+ if hasattr(self, 'cph_label'):
+ self.cph_label.setText(f"Hit: {self._stats.cost_per_hit:.4f}")
+ if hasattr(self, 'cphl_label'):
+ self.cphl_label.setText(f"Heal: {self._stats.cost_per_heal:.4f}")
+
+ # Gear
+ if hasattr(self, 'weapon_label'):
+ self.weapon_label.setText(f"🔫 {self._stats.current_weapon[:20]}")
+ if hasattr(self, 'armor_label'):
+ self.armor_label.setText(f"🛡️ {self._stats.current_armor[:20]}")
+ if hasattr(self, 'loadout_label'):
+ self.loadout_label.setText(f"📋 {self._stats.current_loadout[:20]}")
+
+ # Cost breakdown
+ if hasattr(self, 'wep_cost_label'):
+ self.wep_cost_label.setText(f"{self._stats.weapon_cost_total:.2f}")
+ if hasattr(self, 'arm_cost_label'):
+ self.arm_cost_label.setText(f"{self._stats.armor_cost_total:.2f}")
+ if hasattr(self, 'heal_cost_label'):
+ self.heal_cost_label.setText(f"{self._stats.healing_cost_total:.2f}")
+
+ # Combat
+ if hasattr(self, 'kills_label'):
+ self.kills_label.setText(f"Kills: {self._stats.kills}")
+ if hasattr(self, 'globals_label'):
+ self.globals_label.setText(f"Globals: {self._stats.globals_count}")
+
+ # === Public Update Methods ===
+
+ def update_loot(self, value_ped: Decimal):
+ """Update loot value."""
+ if self.session_active:
+ self._stats.loot_total += value_ped
+ self._stats.recalculate()
+ self._refresh_display()
+
+ def update_weapon_cost(self, cost_ped: Decimal):
+ """Update weapon cost."""
+ if self.session_active:
+ self._stats.weapon_cost_total += cost_ped
+ self._stats.cost_total += cost_ped
+ self._stats.recalculate()
+ self._refresh_display()
+
+ def update_armor_cost(self, cost_ped: Decimal):
+ """Update armor cost."""
+ if self.session_active:
+ self._stats.armor_cost_total += cost_ped
+ self._stats.cost_total += cost_ped
+ self._stats.recalculate()
+ self._refresh_display()
+
+ def update_healing_cost(self, cost_ped: Decimal):
+ """Update healing cost."""
+ if self.session_active:
+ self._stats.healing_cost_total += cost_ped
+ self._stats.cost_total += cost_ped
+ self._stats.recalculate()
+ self._refresh_display()
+
+ def update_kills(self, count: int = 1):
+ """Update kill count."""
+ if self.session_active:
+ self._stats.kills += count
+ self._refresh_display()
+
+ def update_globals(self):
+ """Update global count."""
+ if self.session_active:
+ self._stats.globals_count += 1
+ self._refresh_display()
+
+ # === Mouse Handling ===
+
+ def mousePressEvent(self, event: QMouseEvent):
+ if event.button() == Qt.MouseButton.LeftButton:
+ if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
+ self._dragging = True
+ self._drag_offset = event.pos()
+ self.setCursor(Qt.CursorShape.ClosedHandCursor)
+ event.accept()
+
+ def mouseMoveEvent(self, event: QMouseEvent):
+ if self._dragging:
+ new_pos = self.mapToGlobal(event.pos()) - self._drag_offset
+ self.move(new_pos)
+ self.position_changed.emit(new_pos)
+ event.accept()
+
+ def mouseReleaseEvent(self, event: QMouseEvent):
+ if event.button() == Qt.MouseButton.LeftButton and self._dragging:
+ self._dragging = False
+ self.setCursor(Qt.CursorShape.ArrowCursor)
+ self._save_position()
+ event.accept()
+
+ def enterEvent(self, event):
+ """Mouse entered - enable interaction."""
+ super().enterEvent(event)
+
+ def leaveEvent(self, event):
+ """Mouse left - disable interaction."""
+ super().leaveEvent(event)
+
+ def _save_position(self):
+ """Save window position."""
+ try:
+ pos_file = self.config_path.parent / "hud_position.json"
+ with open(pos_file, 'w') as f:
+ json.dump({'x': self.x(), 'y': self.y()}, f)
+ except:
+ pass
+
+ def _load_position(self):
+ """Load window position."""
+ try:
+ pos_file = self.config_path.parent / "hud_position.json"
+ if pos_file.exists():
+ with open(pos_file, 'r') as f:
+ pos = json.load(f)
+ self.move(pos.get('x', 100), pos.get('y', 100))
+ else:
+ screen = QApplication.primaryScreen().geometry()
+ self.move(screen.width() - 360, 50)
+ except:
+ self.move(100, 100)