EU-Utility/plugins/builtin/dashboard_widget/main.py

323 lines
11 KiB
Python

"""
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