323 lines
11 KiB
Python
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
|