fix: add missing UI elements for damage stats and shrapnel

- Add damage_dealt and damage_taken to HUDStats
- Add shrapnel_total to HUDStats
- Add UI elements for damage stats in _setup_ui
- Add UI element for shrapnel in _setup_ui
- Update window size calculation for new elements
- Update _refresh_display to show damage and shrapnel
- Add update_damage() and update_shrapnel() public methods
This commit is contained in:
LemonNexus 2026-02-09 22:52:33 +00:00
parent 5c4ce8f307
commit 61d2ad2019
1 changed files with 191 additions and 128 deletions

View File

@ -15,7 +15,7 @@ from typing import Optional, Dict, Any, List, Set
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes import wintypes from ctypes import wintypes
user32 = ctypes.windll.user32 user32 = ctypes.windll.user32
GetForegroundWindow = user32.GetForegroundWindow GetForegroundWindow = user32.GetForegroundWindow
GetForegroundWindow.restype = wintypes.HWND GetForegroundWindow.restype = wintypes.HWND
@ -45,9 +45,9 @@ class HUDConfig:
show_cost_metrics: bool = True # Cost per shot/hit/heal show_cost_metrics: bool = True # Cost per shot/hit/heal
show_shrapnel: bool = False show_shrapnel: bool = False
show_gear_info: bool = True show_gear_info: bool = True
compact_mode: bool = False compact_mode: bool = False
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
'show_session_time': self.show_session_time, 'show_session_time': self.show_session_time,
@ -62,7 +62,7 @@ class HUDConfig:
'show_gear_info': self.show_gear_info, 'show_gear_info': self.show_gear_info,
'compact_mode': self.compact_mode, 'compact_mode': self.compact_mode,
} }
@classmethod @classmethod
def from_dict(cls, data: dict) -> "HUDConfig": def from_dict(cls, data: dict) -> "HUDConfig":
return cls( return cls(
@ -84,34 +84,41 @@ class HUDConfig:
class HUDStats: class HUDStats:
"""Simplified stats for HUD display.""" """Simplified stats for HUD display."""
session_time: timedelta = field(default_factory=lambda: timedelta(0)) session_time: timedelta = field(default_factory=lambda: timedelta(0))
# Financial (core) # Financial (core)
loot_total: Decimal = Decimal('0.0') loot_total: Decimal = Decimal('0.0')
cost_total: Decimal = Decimal('0.0') cost_total: Decimal = Decimal('0.0')
profit_loss: Decimal = Decimal('0.0') profit_loss: Decimal = Decimal('0.0')
return_percentage: Decimal = Decimal('0.0') return_percentage: Decimal = Decimal('0.0')
# Cost breakdown (optional) # Cost breakdown (optional)
weapon_cost_total: Decimal = Decimal('0.0') weapon_cost_total: Decimal = Decimal('0.0')
armor_cost_total: Decimal = Decimal('0.0') armor_cost_total: Decimal = Decimal('0.0')
healing_cost_total: Decimal = Decimal('0.0') healing_cost_total: Decimal = Decimal('0.0')
# Combat (optional) # Combat (optional)
kills: int = 0 kills: int = 0
globals_count: int = 0 globals_count: int = 0
hofs_count: int = 0 hofs_count: int = 0
# Damage stats (optional)
damage_dealt: Decimal = Decimal('0.0')
damage_taken: Decimal = Decimal('0.0')
# Shrapnel (optional)
shrapnel_total: Decimal = Decimal('0.0')
# Cost metrics (core) # Cost metrics (core)
cost_per_shot: Decimal = Decimal('0.0') cost_per_shot: Decimal = Decimal('0.0')
cost_per_hit: Decimal = Decimal('0.0') cost_per_hit: Decimal = Decimal('0.0')
cost_per_heal: Decimal = Decimal('0.0') cost_per_heal: Decimal = Decimal('0.0')
# Gear # Gear
current_weapon: str = "None" current_weapon: str = "None"
current_armor: str = "None" current_armor: str = "None"
current_fap: str = "None" current_fap: str = "None"
current_loadout: str = "None" current_loadout: str = "None"
def recalculate(self): def recalculate(self):
"""Recalculate derived values.""" """Recalculate derived values."""
self.profit_loss = self.loot_total - self.cost_total self.profit_loss = self.loot_total - self.cost_total
@ -119,7 +126,7 @@ class HUDStats:
self.return_percentage = (self.loot_total / self.cost_total) * Decimal('100') self.return_percentage = (self.loot_total / self.cost_total) * Decimal('100')
else: else:
self.return_percentage = Decimal('0') self.return_percentage = Decimal('0')
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
'session_time': str(self.session_time), 'session_time': str(self.session_time),
@ -145,93 +152,93 @@ class HUDStats:
class HUDSettingsDialog(QDialog): class HUDSettingsDialog(QDialog):
"""Dialog to configure which HUD elements to show.""" """Dialog to configure which HUD elements to show."""
def __init__(self, config: HUDConfig, parent=None): def __init__(self, config: HUDConfig, parent=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("HUD Settings") self.setWindowTitle("HUD Settings")
self.setMinimumWidth(300) self.setMinimumWidth(300)
self.config = config self.config = config
self._setup_ui() self._setup_ui()
def _setup_ui(self): def _setup_ui(self):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
scroll = QScrollArea() scroll = QScrollArea()
scroll.setWidgetResizable(True) scroll.setWidgetResizable(True)
content = QWidget() content = QWidget()
form = QFormLayout(content) form = QFormLayout(content)
# Core stats (always recommended) # Core stats (always recommended)
form.addRow(QLabel("<b>Core Stats (Recommended)</b>")) form.addRow(QLabel("<b>Core Stats (Recommended)</b>"))
self.cb_time = QCheckBox("Session Time") self.cb_time = QCheckBox("Session Time")
self.cb_time.setChecked(self.config.show_session_time) self.cb_time.setChecked(self.config.show_session_time)
form.addRow(self.cb_time) form.addRow(self.cb_time)
self.cb_profit = QCheckBox("Profit/Loss") self.cb_profit = QCheckBox("Profit/Loss")
self.cb_profit.setChecked(self.config.show_profit_loss) self.cb_profit.setChecked(self.config.show_profit_loss)
form.addRow(self.cb_profit) form.addRow(self.cb_profit)
self.cb_return = QCheckBox("Return %") self.cb_return = QCheckBox("Return %")
self.cb_return.setChecked(self.config.show_return_pct) self.cb_return.setChecked(self.config.show_return_pct)
form.addRow(self.cb_return) form.addRow(self.cb_return)
self.cb_total_cost = QCheckBox("Total Cost") self.cb_total_cost = QCheckBox("Total Cost")
self.cb_total_cost.setChecked(self.config.show_total_cost) self.cb_total_cost.setChecked(self.config.show_total_cost)
form.addRow(self.cb_total_cost) form.addRow(self.cb_total_cost)
self.cb_cost_metrics = QCheckBox("Cost per Shot/Hit/Heal") self.cb_cost_metrics = QCheckBox("Cost per Shot/Hit/Heal")
self.cb_cost_metrics.setChecked(self.config.show_cost_metrics) self.cb_cost_metrics.setChecked(self.config.show_cost_metrics)
form.addRow(self.cb_cost_metrics) form.addRow(self.cb_cost_metrics)
self.cb_gear = QCheckBox("Current Gear") self.cb_gear = QCheckBox("Current Gear")
self.cb_gear.setChecked(self.config.show_gear_info) self.cb_gear.setChecked(self.config.show_gear_info)
form.addRow(self.cb_gear) form.addRow(self.cb_gear)
# Optional stats # Optional stats
form.addRow(QLabel("<b>Optional Stats</b>")) form.addRow(QLabel("<b>Optional Stats</b>"))
self.cb_cost_breakdown = QCheckBox("Cost Breakdown (Weapon/Armor/Heal)") self.cb_cost_breakdown = QCheckBox("Cost Breakdown (Weapon/Armor/Heal)")
self.cb_cost_breakdown.setChecked(self.config.show_cost_breakdown) self.cb_cost_breakdown.setChecked(self.config.show_cost_breakdown)
form.addRow(self.cb_cost_breakdown) form.addRow(self.cb_cost_breakdown)
self.cb_combat = QCheckBox("Combat Stats (Kills, Globals)") self.cb_combat = QCheckBox("Combat Stats (Kills, Globals)")
self.cb_combat.setChecked(self.config.show_combat_stats) self.cb_combat.setChecked(self.config.show_combat_stats)
form.addRow(self.cb_combat) form.addRow(self.cb_combat)
self.cb_damage = QCheckBox("Damage Stats (Dealt/Taken)") self.cb_damage = QCheckBox("Damage Stats (Dealt/Taken)")
self.cb_damage.setChecked(self.config.show_damage_stats) self.cb_damage.setChecked(self.config.show_damage_stats)
form.addRow(self.cb_damage) form.addRow(self.cb_damage)
self.cb_shrapnel = QCheckBox("Shrapnel Amount") self.cb_shrapnel = QCheckBox("Shrapnel Amount")
self.cb_shrapnel.setChecked(self.config.show_shrapnel) self.cb_shrapnel.setChecked(self.config.show_shrapnel)
form.addRow(self.cb_shrapnel) form.addRow(self.cb_shrapnel)
# Display mode # Display mode
form.addRow(QLabel("<b>Display Mode</b>")) form.addRow(QLabel("<b>Display Mode</b>"))
self.cb_compact = QCheckBox("Compact Mode (smaller font)") self.cb_compact = QCheckBox("Compact Mode (smaller font)")
self.cb_compact.setChecked(self.config.compact_mode) self.cb_compact.setChecked(self.config.compact_mode)
form.addRow(self.cb_compact) form.addRow(self.cb_compact)
scroll.setWidget(content) scroll.setWidget(content)
layout.addWidget(scroll) layout.addWidget(scroll)
# Buttons # Buttons
btn_layout = QHBoxLayout() btn_layout = QHBoxLayout()
btn_layout.addStretch() btn_layout.addStretch()
save_btn = QPushButton("Save") save_btn = QPushButton("Save")
save_btn.clicked.connect(self._on_save) save_btn.clicked.connect(self._on_save)
btn_layout.addWidget(save_btn) btn_layout.addWidget(save_btn)
cancel_btn = QPushButton("Cancel") cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.reject) cancel_btn.clicked.connect(self.reject)
btn_layout.addWidget(cancel_btn) btn_layout.addWidget(cancel_btn)
layout.addLayout(btn_layout) layout.addLayout(btn_layout)
def _on_save(self): def _on_save(self):
self.config.show_session_time = self.cb_time.isChecked() self.config.show_session_time = self.cb_time.isChecked()
self.config.show_profit_loss = self.cb_profit.isChecked() self.config.show_profit_loss = self.cb_profit.isChecked()
@ -245,7 +252,7 @@ class HUDSettingsDialog(QDialog):
self.config.show_shrapnel = self.cb_shrapnel.isChecked() self.config.show_shrapnel = self.cb_shrapnel.isChecked()
self.config.compact_mode = self.cb_compact.isChecked() self.config.compact_mode = self.cb_compact.isChecked()
self.accept() self.accept()
def get_config(self) -> HUDConfig: def get_config(self) -> HUDConfig:
return self.config return self.config
@ -253,44 +260,44 @@ class HUDSettingsDialog(QDialog):
class HUDOverlay(QWidget): class HUDOverlay(QWidget):
""" """
Clean, customizable HUD Overlay for Lemontropia Suite. Clean, customizable HUD Overlay for Lemontropia Suite.
Features: Features:
- Collapsible sections - Collapsible sections
- Customizable display elements - Customizable display elements
- Compact mode - Compact mode
- Draggable with Ctrl+click - Draggable with Ctrl+click
""" """
position_changed = pyqtSignal(QPoint) position_changed = pyqtSignal(QPoint)
stats_updated = pyqtSignal(dict) stats_updated = pyqtSignal(dict)
def __init__(self, parent=None, config_path: Optional[str] = None): def __init__(self, parent=None, config_path: Optional[str] = None):
super().__init__(parent) super().__init__(parent)
self.config_path = Path(config_path) if config_path else Path.home() / ".lemontropia" / "hud_config.json" 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) self.config_path.parent.mkdir(parents=True, exist_ok=True)
# Load HUD config # Load HUD config
self.hud_config = self._load_hud_config() self.hud_config = self._load_hud_config()
# Session state # Session state
self._session_start: Optional[datetime] = None self._session_start: Optional[datetime] = None
self._stats = HUDStats() self._stats = HUDStats()
self.session_active = False self.session_active = False
# Drag state # Drag state
self._dragging = False self._dragging = False
self._drag_offset = QPoint() self._drag_offset = QPoint()
# Setup # Setup
self._setup_window() self._setup_window()
self._setup_ui() self._setup_ui()
self._load_position() self._load_position()
# Timer for session time # Timer for session time
self._timer = QTimer(self) self._timer = QTimer(self)
self._timer.timeout.connect(self._update_session_time) self._timer.timeout.connect(self._update_session_time)
def _load_hud_config(self) -> HUDConfig: def _load_hud_config(self) -> HUDConfig:
"""Load HUD configuration.""" """Load HUD configuration."""
try: try:
@ -301,7 +308,7 @@ class HUDOverlay(QWidget):
except Exception as e: except Exception as e:
pass pass
return HUDConfig() # Default config return HUDConfig() # Default config
def _save_hud_config(self): def _save_hud_config(self):
"""Save HUD configuration.""" """Save HUD configuration."""
try: try:
@ -309,7 +316,7 @@ class HUDOverlay(QWidget):
json.dump(self.hud_config.to_dict(), f, indent=2) json.dump(self.hud_config.to_dict(), f, indent=2)
except Exception as e: except Exception as e:
pass pass
def _setup_window(self): def _setup_window(self):
"""Configure window properties.""" """Configure window properties."""
self.setWindowFlags( self.setWindowFlags(
@ -320,30 +327,30 @@ class HUDOverlay(QWidget):
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setMouseTracking(True) self.setMouseTracking(True)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
def _setup_ui(self): def _setup_ui(self):
"""Build the HUD UI.""" """Build the HUD UI."""
# Create container first # Create container first
self.container = QFrame(self) self.container = QFrame(self)
self.container.setObjectName("hudContainer") self.container.setObjectName("hudContainer")
self.container.setStyleSheet(self._get_stylesheet()) self.container.setStyleSheet(self._get_stylesheet())
# Now calculate and set size # Now calculate and set size
self._update_window_size() self._update_window_size()
layout = QVBoxLayout(self.container) layout = QVBoxLayout(self.container)
layout.setContentsMargins(12, 12, 12, 8) layout.setContentsMargins(12, 12, 12, 8)
layout.setSpacing(4) layout.setSpacing(4)
# === HEADER: Status + Gear === # === HEADER: Status + Gear ===
header = QHBoxLayout() header = QHBoxLayout()
self.status_label = QLabel("● Ready") self.status_label = QLabel("● Ready")
self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;") self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;")
header.addWidget(self.status_label) header.addWidget(self.status_label)
header.addStretch() header.addStretch()
# Settings button # Settings button
self.settings_btn = QPushButton("⚙️") self.settings_btn = QPushButton("⚙️")
self.settings_btn.setFixedSize(24, 24) self.settings_btn.setFixedSize(24, 24)
@ -360,141 +367,168 @@ class HUDOverlay(QWidget):
""") """)
self.settings_btn.clicked.connect(self._show_settings) self.settings_btn.clicked.connect(self._show_settings)
header.addWidget(self.settings_btn) header.addWidget(self.settings_btn)
layout.addLayout(header) layout.addLayout(header)
# === GEAR INFO (if enabled) === # === GEAR INFO (if enabled) ===
if self.hud_config.show_gear_info: if self.hud_config.show_gear_info:
self.gear_frame = QFrame() self.gear_frame = QFrame()
gear_layout = QVBoxLayout(self.gear_frame) gear_layout = QVBoxLayout(self.gear_frame)
gear_layout.setContentsMargins(0, 0, 0, 0) gear_layout.setContentsMargins(0, 0, 0, 0)
gear_layout.setSpacing(2) gear_layout.setSpacing(2)
self.weapon_label = QLabel("🔫 None") self.weapon_label = QLabel("🔫 None")
self.armor_label = QLabel("🛡️ None") self.armor_label = QLabel("🛡️ None")
self.loadout_label = QLabel("📋 No Loadout") self.loadout_label = QLabel("📋 No Loadout")
gear_layout.addWidget(self.weapon_label) gear_layout.addWidget(self.weapon_label)
gear_layout.addWidget(self.armor_label) gear_layout.addWidget(self.armor_label)
gear_layout.addWidget(self.loadout_label) gear_layout.addWidget(self.loadout_label)
layout.addWidget(self.gear_frame) layout.addWidget(self.gear_frame)
# Separator # Separator
sep = QFrame() sep = QFrame()
sep.setFrameShape(QFrame.Shape.HLine) sep.setFrameShape(QFrame.Shape.HLine)
sep.setStyleSheet("background-color: rgba(255, 215, 0, 50);") sep.setStyleSheet("background-color: rgba(255, 215, 0, 50);")
sep.setFixedHeight(1) sep.setFixedHeight(1)
layout.addWidget(sep) layout.addWidget(sep)
# === CORE: P/L + Return % === # === CORE: P/L + Return % ===
if self.hud_config.show_profit_loss or self.hud_config.show_return_pct: if self.hud_config.show_profit_loss or self.hud_config.show_return_pct:
core_frame = QFrame() core_frame = QFrame()
core_layout = QHBoxLayout(core_frame) core_layout = QHBoxLayout(core_frame)
core_layout.setContentsMargins(0, 4, 0, 4) core_layout.setContentsMargins(0, 4, 0, 4)
if self.hud_config.show_profit_loss: if self.hud_config.show_profit_loss:
self.profit_label = QLabel("P/L: 0.00 PED") self.profit_label = QLabel("P/L: 0.00 PED")
self.profit_label.setStyleSheet("font-size: 18px; font-weight: bold;") self.profit_label.setStyleSheet("font-size: 18px; font-weight: bold;")
core_layout.addWidget(self.profit_label) core_layout.addWidget(self.profit_label)
core_layout.addStretch() core_layout.addStretch()
if self.hud_config.show_return_pct: if self.hud_config.show_return_pct:
self.return_label = QLabel("0.0%") self.return_label = QLabel("0.0%")
self.return_label.setStyleSheet("font-size: 16px; font-weight: bold;") self.return_label.setStyleSheet("font-size: 16px; font-weight: bold;")
core_layout.addWidget(self.return_label) core_layout.addWidget(self.return_label)
layout.addWidget(core_frame) layout.addWidget(core_frame)
# === TOTAL COST (if enabled) === # === TOTAL COST (if enabled) ===
if self.hud_config.show_total_cost: if self.hud_config.show_total_cost:
total_cost_frame = QFrame() total_cost_frame = QFrame()
total_cost_layout = QHBoxLayout(total_cost_frame) total_cost_layout = QHBoxLayout(total_cost_frame)
total_cost_layout.setContentsMargins(0, 0, 0, 0) total_cost_layout.setContentsMargins(0, 0, 0, 0)
self.total_cost_label = QLabel("Cost: 0.00 PED") self.total_cost_label = QLabel("Cost: 0.00 PED")
self.total_cost_label.setStyleSheet("font-size: 14px; color: #FFAAAA;") self.total_cost_label.setStyleSheet("font-size: 14px; color: #FFAAAA;")
total_cost_layout.addWidget(self.total_cost_label) total_cost_layout.addWidget(self.total_cost_label)
total_cost_layout.addStretch() total_cost_layout.addStretch()
layout.addWidget(total_cost_frame) layout.addWidget(total_cost_frame)
# === COST METRICS (if enabled) === # === COST METRICS (if enabled) ===
if self.hud_config.show_cost_metrics: if self.hud_config.show_cost_metrics:
metrics_frame = QFrame() metrics_frame = QFrame()
metrics_frame.setStyleSheet("background-color: rgba(0, 0, 0, 100); border-radius: 4px;") metrics_frame.setStyleSheet("background-color: rgba(0, 0, 0, 100); border-radius: 4px;")
metrics_layout = QHBoxLayout(metrics_frame) metrics_layout = QHBoxLayout(metrics_frame)
metrics_layout.setContentsMargins(8, 4, 8, 4) metrics_layout.setContentsMargins(8, 4, 8, 4)
self.cps_label = QLabel("Shot: 0.0000") self.cps_label = QLabel("Shot: 0.0000")
self.cph_label = QLabel("Hit: 0.0000") self.cph_label = QLabel("Hit: 0.0000")
self.cphl_label = QLabel("Heal: 0.0000") self.cphl_label = QLabel("Heal: 0.0000")
for lbl in [self.cps_label, self.cph_label, self.cphl_label]: for lbl in [self.cps_label, self.cph_label, self.cphl_label]:
lbl.setStyleSheet("font-size: 10px; color: #AAAAAA;") lbl.setStyleSheet("font-size: 10px; color: #AAAAAA;")
metrics_layout.addWidget(lbl) metrics_layout.addWidget(lbl)
metrics_layout.addStretch() metrics_layout.addStretch()
layout.addWidget(metrics_frame) layout.addWidget(metrics_frame)
# === OPTIONAL: Cost Breakdown === # === OPTIONAL: Cost Breakdown ===
if self.hud_config.show_cost_breakdown: if self.hud_config.show_cost_breakdown:
breakdown_frame = QFrame() breakdown_frame = QFrame()
breakdown_layout = QFormLayout(breakdown_frame) breakdown_layout = QFormLayout(breakdown_frame)
breakdown_layout.setContentsMargins(0, 0, 0, 0) breakdown_layout.setContentsMargins(0, 0, 0, 0)
breakdown_layout.setSpacing(2) breakdown_layout.setSpacing(2)
self.wep_cost_label = QLabel("0.00") self.wep_cost_label = QLabel("0.00")
self.arm_cost_label = QLabel("0.00") self.arm_cost_label = QLabel("0.00")
self.heal_cost_label = QLabel("0.00") self.heal_cost_label = QLabel("0.00")
breakdown_layout.addRow("Weapon:", self.wep_cost_label) breakdown_layout.addRow("Weapon:", self.wep_cost_label)
breakdown_layout.addRow("Armor:", self.arm_cost_label) breakdown_layout.addRow("Armor:", self.arm_cost_label)
breakdown_layout.addRow("Healing:", self.heal_cost_label) breakdown_layout.addRow("Healing:", self.heal_cost_label)
layout.addWidget(breakdown_frame) layout.addWidget(breakdown_frame)
# === OPTIONAL: Combat Stats === # === OPTIONAL: Combat Stats ===
if self.hud_config.show_combat_stats: if self.hud_config.show_combat_stats:
combat_frame = QFrame() combat_frame = QFrame()
combat_layout = QHBoxLayout(combat_frame) combat_layout = QHBoxLayout(combat_frame)
combat_layout.setContentsMargins(0, 0, 0, 0) combat_layout.setContentsMargins(0, 0, 0, 0)
self.kills_label = QLabel("Kills: 0") self.kills_label = QLabel("Kills: 0")
self.globals_label = QLabel("Globals: 0") self.globals_label = QLabel("Globals: 0")
combat_layout.addWidget(self.kills_label) combat_layout.addWidget(self.kills_label)
combat_layout.addStretch() combat_layout.addStretch()
combat_layout.addWidget(self.globals_label) combat_layout.addWidget(self.globals_label)
layout.addWidget(combat_frame) layout.addWidget(combat_frame)
# === OPTIONAL: Damage Stats ===
if self.hud_config.show_damage_stats:
damage_frame = QFrame()
damage_layout = QHBoxLayout(damage_frame)
damage_layout.setContentsMargins(0, 0, 0, 0)
self.damage_dealt_label = QLabel("Dealt: 0")
self.damage_taken_label = QLabel("Taken: 0")
damage_layout.addWidget(self.damage_dealt_label)
damage_layout.addStretch()
damage_layout.addWidget(self.damage_taken_label)
layout.addWidget(damage_frame)
# === OPTIONAL: Shrapnel ===
if self.hud_config.show_shrapnel:
shrapnel_frame = QFrame()
shrapnel_layout = QHBoxLayout(shrapnel_frame)
shrapnel_layout.setContentsMargins(0, 0, 0, 0)
self.shrapnel_label = QLabel("💎 Shrapnel: 0.00 PED")
shrapnel_layout.addWidget(self.shrapnel_label)
shrapnel_layout.addStretch()
layout.addWidget(shrapnel_frame)
# === FOOTER: Session Time + Drag Hint === # === FOOTER: Session Time + Drag Hint ===
footer = QHBoxLayout() footer = QHBoxLayout()
if self.hud_config.show_session_time: if self.hud_config.show_session_time:
self.time_label = QLabel("00:00:00") self.time_label = QLabel("00:00:00")
self.time_label.setStyleSheet("color: #888; font-size: 11px;") self.time_label.setStyleSheet("color: #888; font-size: 11px;")
footer.addWidget(self.time_label) footer.addWidget(self.time_label)
else: else:
footer.addStretch() footer.addStretch()
footer.addStretch() footer.addStretch()
self.drag_hint = QLabel("Ctrl+drag to move") self.drag_hint = QLabel("Ctrl+drag to move")
self.drag_hint.setStyleSheet("font-size: 9px; color: #666;") self.drag_hint.setStyleSheet("font-size: 9px; color: #666;")
footer.addWidget(self.drag_hint) footer.addWidget(self.drag_hint)
layout.addLayout(footer) layout.addLayout(footer)
# Ensure container is visible # Ensure container is visible
self.container.show() self.container.show()
def _get_stylesheet(self) -> str: def _get_stylesheet(self) -> str:
"""Get stylesheet based on compact mode.""" """Get stylesheet based on compact mode."""
font_size = "11px" if self.hud_config.compact_mode else "12px" font_size = "11px" if self.hud_config.compact_mode else "12px"
return f""" return f"""
#hudContainer {{ #hudContainer {{
background-color: rgba(0, 0, 0, 180); background-color: rgba(0, 0, 0, 180);
@ -507,11 +541,11 @@ class HUDOverlay(QWidget):
font-size: {font_size}; font-size: {font_size};
}} }}
""" """
def _update_window_size(self): def _update_window_size(self):
"""Calculate window size based on enabled features.""" """Calculate window size based on enabled features."""
height = 80 # Base height height = 80 # Base height
if self.hud_config.show_gear_info: if self.hud_config.show_gear_info:
height += 70 height += 70
if self.hud_config.show_profit_loss or self.hud_config.show_return_pct: if self.hud_config.show_profit_loss or self.hud_config.show_return_pct:
@ -524,14 +558,18 @@ class HUDOverlay(QWidget):
height += 70 height += 70
if self.hud_config.show_combat_stats: if self.hud_config.show_combat_stats:
height += 25 height += 25
if self.hud_config.show_damage_stats:
height += 25
if self.hud_config.show_shrapnel:
height += 25
if self.hud_config.show_session_time: if self.hud_config.show_session_time:
height += 20 height += 20
width = 280 if self.hud_config.compact_mode else 320 width = 280 if self.hud_config.compact_mode else 320
self.setFixedSize(width, height) self.setFixedSize(width, height)
self.container.setFixedSize(width, height) self.container.setFixedSize(width, height)
def _show_settings(self): def _show_settings(self):
"""Show settings dialog.""" """Show settings dialog."""
dialog = HUDSettingsDialog(self.hud_config, self) dialog = HUDSettingsDialog(self.hud_config, self)
@ -540,21 +578,21 @@ class HUDOverlay(QWidget):
self._save_hud_config() self._save_hud_config()
# Rebuild UI with new settings # Rebuild UI with new settings
self._rebuild_ui() self._rebuild_ui()
def _rebuild_ui(self): def _rebuild_ui(self):
"""Rebuild UI with current config.""" """Rebuild UI with current config."""
# Hide first to avoid visual glitch # Hide first to avoid visual glitch
self.hide() self.hide()
# Delete old container # Delete old container
if hasattr(self, 'container') and self.container: if hasattr(self, 'container') and self.container:
self.container.deleteLater() self.container.deleteLater()
self.container = None self.container = None
# Small delay to ensure cleanup # Small delay to ensure cleanup
from PyQt6.QtCore import QTimer from PyQt6.QtCore import QTimer
QTimer.singleShot(10, self._do_rebuild) QTimer.singleShot(10, self._do_rebuild)
def _do_rebuild(self): def _do_rebuild(self):
"""Actually rebuild the UI.""" """Actually rebuild the UI."""
try: try:
@ -565,9 +603,9 @@ class HUDOverlay(QWidget):
logger.error(f"Error rebuilding HUD: {e}") logger.error(f"Error rebuilding HUD: {e}")
# Try to recover # Try to recover
self.show() self.show()
# === Session Management === # === Session Management ===
def start_session(self, weapon: str = "Unknown", armor: str = "None", def start_session(self, weapon: str = "Unknown", armor: str = "None",
fap: str = "None", loadout: str = "Default", fap: str = "None", loadout: str = "Default",
weapon_dpp: Decimal = Decimal('0.0'), weapon_dpp: Decimal = Decimal('0.0'),
@ -586,35 +624,35 @@ class HUDOverlay(QWidget):
self._stats.cost_per_hit = cost_per_hit self._stats.cost_per_hit = cost_per_hit
self._stats.cost_per_heal = cost_per_heal self._stats.cost_per_heal = cost_per_heal
self.session_active = True self.session_active = True
self._timer.start(1000) self._timer.start(1000)
self._refresh_display() self._refresh_display()
self.status_label.setText("● Live") self.status_label.setText("● Live")
self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;") self.status_label.setStyleSheet("color: #7FFF7F; font-weight: bold;")
def end_session(self) -> None: def end_session(self) -> None:
"""Alias for stop_session for backward compatibility.""" """Alias for stop_session for backward compatibility."""
self.stop_session() self.stop_session()
def stop_session(self) -> None: def stop_session(self) -> None:
"""Stop the current session.""" """Stop the current session."""
self.session_active = False self.session_active = False
self._timer.stop() self._timer.stop()
self.status_label.setText("● Stopped") self.status_label.setText("● Stopped")
self.status_label.setStyleSheet("color: #FF7F7F; font-weight: bold;") self.status_label.setStyleSheet("color: #FF7F7F; font-weight: bold;")
# === Event Handlers === # === Event Handlers ===
def _update_session_time(self): def _update_session_time(self):
"""Update session time display.""" """Update session time display."""
if self._session_start and self.session_active: if self._session_start and self.session_active:
elapsed = datetime.now() - self._session_start elapsed = datetime.now() - self._session_start
hours, remainder = divmod(elapsed.seconds, 3600) hours, remainder = divmod(elapsed.seconds, 3600)
minutes, seconds = divmod(remainder, 60) minutes, seconds = divmod(remainder, 60)
if hasattr(self, 'time_label'): if hasattr(self, 'time_label'):
self.time_label.setText(f"{hours:02d}:{minutes:02d}:{seconds:02d}") self.time_label.setText(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
def _refresh_display(self): def _refresh_display(self):
"""Refresh all display labels.""" """Refresh all display labels."""
# Profit/Loss # Profit/Loss
@ -623,7 +661,7 @@ class HUDOverlay(QWidget):
color = "#7FFF7F" if profit >= 0 else "#FF7F7F" color = "#7FFF7F" if profit >= 0 else "#FF7F7F"
self.profit_label.setText(f"{profit:+.2f} PED") self.profit_label.setText(f"{profit:+.2f} PED")
self.profit_label.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {color};") self.profit_label.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {color};")
# Return % # Return %
if hasattr(self, 'return_label'): if hasattr(self, 'return_label'):
ret = self._stats.return_percentage ret = self._stats.return_percentage
@ -635,11 +673,11 @@ class HUDOverlay(QWidget):
color = "#FF7F7F" color = "#FF7F7F"
self.return_label.setText(f"{ret:.1f}%") self.return_label.setText(f"{ret:.1f}%")
self.return_label.setStyleSheet(f"font-size: 16px; font-weight: bold; color: {color};") self.return_label.setStyleSheet(f"font-size: 16px; font-weight: bold; color: {color};")
# Total Cost # Total Cost
if hasattr(self, 'total_cost_label'): if hasattr(self, 'total_cost_label'):
self.total_cost_label.setText(f"Cost: {self._stats.cost_total:.2f} PED") self.total_cost_label.setText(f"Cost: {self._stats.cost_total:.2f} PED")
# Cost metrics # Cost metrics
if hasattr(self, 'cps_label'): if hasattr(self, 'cps_label'):
self.cps_label.setText(f"Shot: {self._stats.cost_per_shot:.4f}") self.cps_label.setText(f"Shot: {self._stats.cost_per_shot:.4f}")
@ -647,7 +685,7 @@ class HUDOverlay(QWidget):
self.cph_label.setText(f"Hit: {self._stats.cost_per_hit:.4f}") self.cph_label.setText(f"Hit: {self._stats.cost_per_hit:.4f}")
if hasattr(self, 'cphl_label'): if hasattr(self, 'cphl_label'):
self.cphl_label.setText(f"Heal: {self._stats.cost_per_heal:.4f}") self.cphl_label.setText(f"Heal: {self._stats.cost_per_heal:.4f}")
# Gear # Gear
if hasattr(self, 'weapon_label'): if hasattr(self, 'weapon_label'):
self.weapon_label.setText(f"🔫 {self._stats.current_weapon[:20]}") self.weapon_label.setText(f"🔫 {self._stats.current_weapon[:20]}")
@ -655,7 +693,7 @@ class HUDOverlay(QWidget):
self.armor_label.setText(f"🛡️ {self._stats.current_armor[:20]}") self.armor_label.setText(f"🛡️ {self._stats.current_armor[:20]}")
if hasattr(self, 'loadout_label'): if hasattr(self, 'loadout_label'):
self.loadout_label.setText(f"📋 {self._stats.current_loadout[:20]}") self.loadout_label.setText(f"📋 {self._stats.current_loadout[:20]}")
# Cost breakdown # Cost breakdown
if hasattr(self, 'wep_cost_label'): if hasattr(self, 'wep_cost_label'):
self.wep_cost_label.setText(f"{self._stats.weapon_cost_total:.2f}") self.wep_cost_label.setText(f"{self._stats.weapon_cost_total:.2f}")
@ -663,22 +701,32 @@ class HUDOverlay(QWidget):
self.arm_cost_label.setText(f"{self._stats.armor_cost_total:.2f}") self.arm_cost_label.setText(f"{self._stats.armor_cost_total:.2f}")
if hasattr(self, 'heal_cost_label'): if hasattr(self, 'heal_cost_label'):
self.heal_cost_label.setText(f"{self._stats.healing_cost_total:.2f}") self.heal_cost_label.setText(f"{self._stats.healing_cost_total:.2f}")
# Combat # Combat
if hasattr(self, 'kills_label'): if hasattr(self, 'kills_label'):
self.kills_label.setText(f"Kills: {self._stats.kills}") self.kills_label.setText(f"Kills: {self._stats.kills}")
if hasattr(self, 'globals_label'): if hasattr(self, 'globals_label'):
self.globals_label.setText(f"Globals: {self._stats.globals_count}") self.globals_label.setText(f"Globals: {self._stats.globals_count}")
# Damage stats
if hasattr(self, 'damage_dealt_label'):
self.damage_dealt_label.setText(f"Dealt: {int(self._stats.damage_dealt)}")
if hasattr(self, 'damage_taken_label'):
self.damage_taken_label.setText(f"Taken: {int(self._stats.damage_taken)}")
# Shrapnel
if hasattr(self, 'shrapnel_label'):
self.shrapnel_label.setText(f"💎 Shrapnel: {self._stats.shrapnel_total:.2f} PED")
# === Public Update Methods === # === Public Update Methods ===
def update_loot(self, value_ped: Decimal): def update_loot(self, value_ped: Decimal):
"""Update loot value.""" """Update loot value."""
if self.session_active: if self.session_active:
self._stats.loot_total += value_ped self._stats.loot_total += value_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self._refresh_display()
def update_weapon_cost(self, cost_ped: Decimal): def update_weapon_cost(self, cost_ped: Decimal):
"""Update weapon cost.""" """Update weapon cost."""
if self.session_active: if self.session_active:
@ -686,7 +734,7 @@ class HUDOverlay(QWidget):
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self._refresh_display()
def update_armor_cost(self, cost_ped: Decimal): def update_armor_cost(self, cost_ped: Decimal):
"""Update armor cost.""" """Update armor cost."""
if self.session_active: if self.session_active:
@ -694,7 +742,7 @@ class HUDOverlay(QWidget):
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self._refresh_display()
def update_healing_cost(self, cost_ped: Decimal): def update_healing_cost(self, cost_ped: Decimal):
"""Update healing cost.""" """Update healing cost."""
if self.session_active: if self.session_active:
@ -702,21 +750,36 @@ class HUDOverlay(QWidget):
self._stats.cost_total += cost_ped self._stats.cost_total += cost_ped
self._stats.recalculate() self._stats.recalculate()
self._refresh_display() self._refresh_display()
def update_kills(self, count: int = 1): def update_kills(self, count: int = 1):
"""Update kill count.""" """Update kill count."""
if self.session_active: if self.session_active:
self._stats.kills += count self._stats.kills += count
self._refresh_display() self._refresh_display()
def update_globals(self): def update_globals(self):
"""Update global count.""" """Update global count."""
if self.session_active: if self.session_active:
self._stats.globals_count += 1 self._stats.globals_count += 1
self._refresh_display() self._refresh_display()
def update_damage(self, dealt: Decimal = Decimal('0'), taken: Decimal = Decimal('0')):
"""Update damage stats."""
if self.session_active:
if dealt > 0:
self._stats.damage_dealt += dealt
if taken > 0:
self._stats.damage_taken += taken
self._refresh_display()
def update_shrapnel(self, amount: Decimal):
"""Update shrapnel amount."""
if self.session_active:
self._stats.shrapnel_total += amount
self._refresh_display()
# === Mouse Handling === # === Mouse Handling ===
def mousePressEvent(self, event: QMouseEvent): def mousePressEvent(self, event: QMouseEvent):
if event.button() == Qt.MouseButton.LeftButton: if event.button() == Qt.MouseButton.LeftButton:
if event.modifiers() == Qt.KeyboardModifier.ControlModifier: if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
@ -724,29 +787,29 @@ class HUDOverlay(QWidget):
self._drag_offset = event.pos() self._drag_offset = event.pos()
self.setCursor(Qt.CursorShape.ClosedHandCursor) self.setCursor(Qt.CursorShape.ClosedHandCursor)
event.accept() event.accept()
def mouseMoveEvent(self, event: QMouseEvent): def mouseMoveEvent(self, event: QMouseEvent):
if self._dragging: if self._dragging:
new_pos = self.mapToGlobal(event.pos()) - self._drag_offset new_pos = self.mapToGlobal(event.pos()) - self._drag_offset
self.move(new_pos) self.move(new_pos)
self.position_changed.emit(new_pos) self.position_changed.emit(new_pos)
event.accept() event.accept()
def mouseReleaseEvent(self, event: QMouseEvent): def mouseReleaseEvent(self, event: QMouseEvent):
if event.button() == Qt.MouseButton.LeftButton and self._dragging: if event.button() == Qt.MouseButton.LeftButton and self._dragging:
self._dragging = False self._dragging = False
self.setCursor(Qt.CursorShape.ArrowCursor) self.setCursor(Qt.CursorShape.ArrowCursor)
self._save_position() self._save_position()
event.accept() event.accept()
def enterEvent(self, event): def enterEvent(self, event):
"""Mouse entered - enable interaction.""" """Mouse entered - enable interaction."""
super().enterEvent(event) super().enterEvent(event)
def leaveEvent(self, event): def leaveEvent(self, event):
"""Mouse left - disable interaction.""" """Mouse left - disable interaction."""
super().leaveEvent(event) super().leaveEvent(event)
def _save_position(self): def _save_position(self):
"""Save window position.""" """Save window position."""
try: try:
@ -755,7 +818,7 @@ class HUDOverlay(QWidget):
json.dump({'x': self.x(), 'y': self.y()}, f) json.dump({'x': self.x(), 'y': self.y()}, f)
except: except:
pass pass
def _load_position(self): def _load_position(self):
"""Load window position.""" """Load window position."""
try: try: