""" EU-Utility Premium - Dashboard Widget Plugin ============================================= A built-in plugin that provides a dashboard for player statistics. Shows skills, loot, and game status in a convenient overlay widget. """ from pathlib import Path from typing import Optional, Dict, Any # Import the plugin API from premium.plugins.api import ( PluginAPI, PluginContext, PluginManifest, PermissionLevel ) # Manifest - REQUIRED manifest = PluginManifest( name="Player Dashboard", version="1.0.0", author="EU-Utility Team", description="Player statistics dashboard with skills, loot, and game status", entry_point="main.py", permissions={PermissionLevel.UI, PermissionLevel.FILE_READ}, tags=["dashboard", "stats", "built-in"], ) class DashboardPlugin(PluginAPI): """Player statistics dashboard plugin. This plugin displays: - Current character name and level - Skill gains (latest and session total) - Loot tracker (recent items and total value) - Game connection status """ manifest = manifest def __init__(self): super().__init__() self._widget = None self._stats = { 'character': None, 'session_loot_value': 0.0, 'session_loot_count': 0, 'session_skills': [], 'last_loot': None, 'last_skill': None, } def on_init(self, ctx: PluginContext) -> None: """Called when plugin is initialized.""" self.ctx = ctx ctx.logger.info("Dashboard plugin initialized") # Load saved stats if any self._load_stats() # Subscribe to events if ctx.event_bus: ctx.event_bus.subscribe('game.loot', self._on_loot) ctx.event_bus.subscribe('game.skill', self._on_skill) ctx.event_bus.subscribe('game.connected', self._on_connected) ctx.event_bus.subscribe('game.disconnected', self._on_disconnected) def on_activate(self) -> None: """Called when plugin is activated.""" self.ctx.logger.info("Dashboard plugin activated") def on_deactivate(self) -> None: """Called when plugin is deactivated.""" self._save_stats() def on_shutdown(self) -> None: """Called when plugin is being unloaded.""" self._save_stats() def create_widget(self) -> Optional[Any]: """Create the dashboard UI widget.""" try: from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QPushButton, QGridLayout ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont # Main widget widget = QWidget() widget.setStyleSheet(""" QWidget { background-color: #1e1e1e; color: #ffffff; font-family: 'Segoe UI', Arial, sans-serif; } QFrame { background-color: #2d2d2d; border-radius: 6px; padding: 10px; } QLabel { color: #ffffff; } .stat-label { color: #888888; font-size: 11px; } .stat-value { font-size: 16px; font-weight: bold; } """) layout = QVBoxLayout(widget) layout.setSpacing(15) layout.setContentsMargins(15, 15, 15, 15) # Header header = QLabel("👤 Player Dashboard") font = QFont("Segoe UI", 14, QFont.Weight.Bold) header.setFont(font) layout.addWidget(header) # Status section status_frame = QFrame() status_layout = QHBoxLayout(status_frame) self.status_label = QLabel("● Disconnected") self.status_label.setStyleSheet("color: #f44336; font-weight: bold;") status_layout.addWidget(self.status_label) self.char_label = QLabel("No character") status_layout.addStretch() status_layout.addWidget(self.char_label) layout.addWidget(status_frame) # Stats grid stats_frame = QFrame() stats_layout = QGridLayout(stats_frame) stats_layout.setSpacing(10) # Session loot loot_title = QLabel("💰 Session Loot") loot_title.setStyleSheet("font-weight: bold;") stats_layout.addWidget(loot_title, 0, 0) self.loot_value_label = QLabel("0.00 PED") self.loot_value_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #4caf50;") stats_layout.addWidget(self.loot_value_label, 1, 0) self.loot_count_label = QLabel("0 items") self.loot_count_label.setStyleSheet("color: #888888;") stats_layout.addWidget(self.loot_count_label, 2, 0) # Skills skills_title = QLabel("📈 Skills") skills_title.setStyleSheet("font-weight: bold;") stats_layout.addWidget(skills_title, 0, 1) self.skills_count_label = QLabel("0 gains") self.skills_count_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2196f3;") stats_layout.addWidget(self.skills_count_label, 1, 1) self.last_skill_label = QLabel("-") self.last_skill_label.setStyleSheet("color: #888888;") stats_layout.addWidget(self.last_skill_label, 2, 1) layout.addWidget(stats_frame) # Recent activity activity_title = QLabel("📝 Recent Activity") activity_title.setStyleSheet("font-weight: bold; margin-top: 10px;") layout.addWidget(activity_title) self.activity_label = QLabel("No activity yet...") self.activity_label.setWordWrap(True) self.activity_label.setStyleSheet("color: #aaaaaa; padding: 5px;") layout.addWidget(self.activity_label) layout.addStretch() # Save reference for updates self._widget = widget self._update_display() return widget except ImportError: self.ctx.logger.warning("PyQt6 not available, cannot create widget") return None # ========== Event Handlers ========== def _on_loot(self, event: Dict[str, Any]) -> None: """Handle loot event.""" item = event.get('item', 'Unknown') value = event.get('value', 0.0) quantity = event.get('quantity', 1) self._stats['session_loot_value'] += value self._stats['session_loot_count'] += quantity self._stats['last_loot'] = { 'item': item, 'value': value, 'quantity': quantity, } self.ctx.logger.info(f"Loot: {item} x{quantity} ({value:.2f} PED)") self._update_display() def _on_skill(self, event: Dict[str, Any]) -> None: """Handle skill gain event.""" skill = event.get('skill', 'Unknown') gain = event.get('gain', 0.0) self._stats['session_skills'].append({ 'skill': skill, 'gain': gain, }) self._stats['last_skill'] = { 'skill': skill, 'gain': gain, } self.ctx.logger.info(f"Skill: {skill} +{gain:.4f}") self._update_display() def _on_connected(self, event: Dict[str, Any]) -> None: """Handle game connection.""" self.ctx.logger.info("Game connected") self._update_status(True) def _on_disconnected(self, event: Dict[str, Any]) -> None: """Handle game disconnection.""" self.ctx.logger.info("Game disconnected") self._update_status(False) # ========== Display Updates ========== def _update_display(self) -> None: """Update the widget display.""" if not self._widget: return try: # Update loot stats self.loot_value_label.setText( f"{self._stats['session_loot_value']:.2f} PED" ) self.loot_count_label.setText( f"{self._stats['session_loot_count']} items" ) # Update skill stats self.skills_count_label.setText( f"{len(self._stats['session_skills'])} gains" ) if self._stats['last_skill']: skill = self._stats['last_skill'] self.last_skill_label.setText( f"{skill['skill']} +{skill['gain']:.4f}" ) # Update activity activity_text = "" if self._stats['last_loot']: loot = self._stats['last_loot'] activity_text += f"Loot: {loot['item']} ({loot['value']:.2f} PED)\n" if self._stats['last_skill']: skill = self._stats['last_skill'] activity_text += f"Skill: {skill['skill']} +{skill['gain']:.4f}" if activity_text: self.activity_label.setText(activity_text) except Exception as e: self.ctx.logger.error(f"Error updating display: {e}") def _update_status(self, connected: bool) -> None: """Update connection status display.""" if not self._widget: return if connected: self.status_label.setText("● Connected") self.status_label.setStyleSheet("color: #4caf50; font-weight: bold;") else: self.status_label.setText("● Disconnected") self.status_label.setStyleSheet("color: #f44336; font-weight: bold;") # ========== Persistence ========== def _load_stats(self) -> None: """Load stats from disk.""" try: import json stats_path = self.ctx.data_dir / "session_stats.json" if stats_path.exists(): with open(stats_path, 'r') as f: saved = json.load(f) self._stats.update(saved) except Exception as e: self.ctx.logger.error(f"Failed to load stats: {e}") def _save_stats(self) -> None: """Save stats to disk.""" try: import json stats_path = self.ctx.data_dir / "session_stats.json" with open(stats_path, 'w') as f: json.dump(self._stats, f, indent=2) except Exception as e: self.ctx.logger.error(f"Failed to save stats: {e}") # Export the plugin class Plugin = DashboardPlugin