diff --git a/ui/hud_overlay_clean.py b/ui/hud_overlay_clean.py index 92c54d6..e0c3dc9 100644 --- a/ui/hud_overlay_clean.py +++ b/ui/hud_overlay_clean.py @@ -15,7 +15,7 @@ from typing import Optional, Dict, Any, List, Set if sys.platform == 'win32': import ctypes from ctypes import wintypes - + user32 = ctypes.windll.user32 GetForegroundWindow = user32.GetForegroundWindow GetForegroundWindow.restype = wintypes.HWND @@ -45,9 +45,9 @@ class HUDConfig: 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, @@ -62,7 +62,7 @@ class HUDConfig: 'show_gear_info': self.show_gear_info, 'compact_mode': self.compact_mode, } - + @classmethod def from_dict(cls, data: dict) -> "HUDConfig": return cls( @@ -84,34 +84,41 @@ class HUDConfig: 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 - + + # 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_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 @@ -119,7 +126,7 @@ class HUDStats: 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), @@ -145,93 +152,93 @@ class HUDStats: 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_total_cost = QCheckBox("Total Cost") self.cb_total_cost.setChecked(self.config.show_total_cost) form.addRow(self.cb_total_cost) - + 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() @@ -245,7 +252,7 @@ class HUDSettingsDialog(QDialog): 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 @@ -253,44 +260,44 @@ class HUDSettingsDialog(QDialog): 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: @@ -301,7 +308,7 @@ class HUDOverlay(QWidget): except Exception as e: pass return HUDConfig() # Default config - + def _save_hud_config(self): """Save HUD configuration.""" try: @@ -309,7 +316,7 @@ class HUDOverlay(QWidget): json.dump(self.hud_config.to_dict(), f, indent=2) except Exception as e: pass - + def _setup_window(self): """Configure window properties.""" self.setWindowFlags( @@ -320,30 +327,30 @@ class HUDOverlay(QWidget): self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setMouseTracking(True) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) - + def _setup_ui(self): """Build the HUD UI.""" # Create container first self.container = QFrame(self) self.container.setObjectName("hudContainer") self.container.setStyleSheet(self._get_stylesheet()) - + # Now calculate and set size self._update_window_size() - + 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) @@ -360,141 +367,168 @@ class HUDOverlay(QWidget): """) 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) - + # === TOTAL COST (if enabled) === if self.hud_config.show_total_cost: total_cost_frame = QFrame() total_cost_layout = QHBoxLayout(total_cost_frame) total_cost_layout.setContentsMargins(0, 0, 0, 0) - + self.total_cost_label = QLabel("Cost: 0.00 PED") self.total_cost_label.setStyleSheet("font-size: 14px; color: #FFAAAA;") total_cost_layout.addWidget(self.total_cost_label) total_cost_layout.addStretch() - + layout.addWidget(total_cost_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) - + + # === 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 = 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) - + # Ensure container is visible self.container.show() - + 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); @@ -507,11 +541,11 @@ class HUDOverlay(QWidget): 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: @@ -524,14 +558,18 @@ class HUDOverlay(QWidget): height += 70 if self.hud_config.show_combat_stats: 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: 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) @@ -540,21 +578,21 @@ class HUDOverlay(QWidget): self._save_hud_config() # Rebuild UI with new settings self._rebuild_ui() - + def _rebuild_ui(self): """Rebuild UI with current config.""" # Hide first to avoid visual glitch self.hide() - + # Delete old container if hasattr(self, 'container') and self.container: self.container.deleteLater() self.container = None - + # Small delay to ensure cleanup from PyQt6.QtCore import QTimer QTimer.singleShot(10, self._do_rebuild) - + def _do_rebuild(self): """Actually rebuild the UI.""" try: @@ -565,9 +603,9 @@ class HUDOverlay(QWidget): logger.error(f"Error rebuilding HUD: {e}") # Try to recover self.show() - + # === Session Management === - + def start_session(self, weapon: str = "Unknown", armor: str = "None", fap: str = "None", loadout: str = "Default", 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_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 end_session(self) -> None: """Alias for stop_session for backward compatibility.""" self.stop_session() - + 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 @@ -623,7 +661,7 @@ class HUDOverlay(QWidget): 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 @@ -635,11 +673,11 @@ class HUDOverlay(QWidget): color = "#FF7F7F" self.return_label.setText(f"{ret:.1f}%") self.return_label.setStyleSheet(f"font-size: 16px; font-weight: bold; color: {color};") - + # Total Cost if hasattr(self, 'total_cost_label'): self.total_cost_label.setText(f"Cost: {self._stats.cost_total:.2f} PED") - + # Cost metrics if hasattr(self, 'cps_label'): 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}") 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]}") @@ -655,7 +693,7 @@ class HUDOverlay(QWidget): 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}") @@ -663,22 +701,32 @@ class HUDOverlay(QWidget): 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}") - + + # 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 === - + 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: @@ -686,7 +734,7 @@ class HUDOverlay(QWidget): 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: @@ -694,7 +742,7 @@ class HUDOverlay(QWidget): 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: @@ -702,21 +750,36 @@ class HUDOverlay(QWidget): 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() - + + 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 === - + def mousePressEvent(self, event: QMouseEvent): if event.button() == Qt.MouseButton.LeftButton: if event.modifiers() == Qt.KeyboardModifier.ControlModifier: @@ -724,29 +787,29 @@ class HUDOverlay(QWidget): 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: @@ -755,7 +818,7 @@ class HUDOverlay(QWidget): json.dump({'x': self.x(), 'y': self.y()}, f) except: pass - + def _load_position(self): """Load window position.""" try: