EU-Utility/docs/PLUGIN_DEVELOPMENT_GUIDE.md

620 lines
14 KiB
Markdown

# EU-Utility Plugin Development Guide
Welcome to plugin development for EU-Utility! This guide covers everything you need to create professional plugins.
## Table of Contents
1. [Quick Start](#quick-start)
2. [Plugin Structure](#plugin-structure)
3. [BasePlugin API Reference](#baseplugin-api-reference)
4. [Core Services](#core-services)
5. [Event System](#event-system)
6. [UI Development](#ui-development)
7. [Best Practices](#best-practices)
8. [Examples](#examples)
9. [Publishing](#publishing)
---
## Quick Start
### Creating Your First Plugin
1. **Create plugin directory:**
```bash
mkdir plugins/my_plugin
touch plugins/my_plugin/__init__.py
touch plugins/my_plugin/plugin.py
```
2. **Basic plugin structure:**
```python
# plugins/my_plugin/plugin.py
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
from plugins.base_plugin import BasePlugin
class MyPlugin(BasePlugin):
"""My first EU-Utility plugin."""
name = "My Plugin"
version = "1.0.0"
author = "Your Name"
description = "Description of what my plugin does"
icon = "target" # Icon name from assets/icons/
hotkey = "ctrl+shift+m" # Optional global hotkey
def initialize(self):
"""Called when plugin is loaded."""
self.log_info("My Plugin initialized!")
# Load saved data
self.settings = self.load_data("settings", {"enabled": True})
def get_ui(self):
"""Return the plugin's UI widget."""
widget = QWidget()
layout = QVBoxLayout(widget)
label = QLabel(f"Hello from {self.name}!")
layout.addWidget(label)
return widget
def on_hotkey(self):
"""Called when hotkey is pressed."""
self.notify_info("Hotkey pressed!", "My Plugin is working!")
def shutdown(self):
"""Called when app closes."""
self.save_data("settings", self.settings)
```
3. **That's it!** Your plugin will auto-load on next start.
---
## Plugin Structure
```
plugins/my_plugin/
├── __init__.py # Makes it a Python package
├── plugin.py # Main plugin code (required)
├── ui/ # UI components (optional)
│ ├── __init__.py
│ └── widgets.py
├── assets/ # Plugin-specific assets (optional)
│ └── icon.svg
└── README.md # Plugin documentation (optional)
```
---
## BasePlugin API Reference
### Lifecycle Methods
| Method | When Called | Purpose |
|--------|-------------|---------|
| `initialize()` | Plugin loaded | Setup, load data, connect to APIs |
| `get_ui()` | UI requested | Return QWidget for display |
| `on_show()` | Overlay shown | Resume updates |
| `on_hide()` | Overlay hidden | Pause updates |
| `on_hotkey()` | Hotkey pressed | Handle global shortcut |
| `shutdown()` | App closing | Cleanup, save data |
### Data Persistence
```python
# Save data (automatically scoped to your plugin)
self.save_data("my_key", {"value": 42})
# Load data with default
config = self.load_data("my_key", {"value": 0})
# Delete data
self.delete_data("my_key")
# Get all keys
keys = self.get_all_data_keys()
```
### Notifications
```python
# Show toast notifications
self.notify("Title", "Message")
self.notify_info("Info", "Something happened")
self.notify_success("Success!", "Operation completed", sound=True)
self.notify_warning("Warning", "Low ammo", sound=False)
self.notify_error("Error!", "Connection failed", sound=True)
```
### Screen Capture & OCR
```python
# Capture full screen
screenshot = self.capture_screen()
# Capture region
region = self.capture_region(100, 100, 400, 300)
# OCR capture
result = self.ocr_capture(region=(100, 100, 400, 300))
text = result['text']
confidence = result['confidence']
```
### Log Reading
```python
# Read recent log lines
lines = self.read_log(lines=100)
# Filtered reading
damage_lines = self.read_log(lines=50, filter_text="damage")
```
### Nexus API
```python
# Search items
items = self.nexus_search("ArMatrix", entity_type="weapons", limit=10)
# Get item details
details = self.nexus_get_item_details("armatrix_lp-35")
# Get market data
market = self.nexus_get_market_data("armatrix_lp-35")
```
### HTTP Requests
```python
# GET with caching
response = self.http_get("https://api.example.com/data", cache_ttl=300)
if response['status_code'] == 200:
data = response['json']
```
### Background Tasks
```python
# Run in background
def heavy_calculation(data):
return process(data)
task_id = self.run_in_background(
heavy_calculation,
large_dataset,
priority='high',
on_complete=lambda result: self.update_ui(result),
on_error=lambda error: self.show_error(error)
)
# Scheduled tasks
self.schedule_task(
delay_ms=5000, # Start in 5 seconds
func=self.refresh_data,
periodic=True,
interval_ms=30000 # Repeat every 30 seconds
)
# Cancel task
self.cancel_task(task_id)
```
### Audio
```python
# Play sounds
self.play_sound('global') # Predefined sounds
self.play_sound('hof')
self.play_sound('skill_gain')
self.play_sound('/path/to/custom.wav')
# Volume control
self.set_volume(0.8)
volume = self.get_volume()
self.mute()
self.unmute()
```
### Clipboard
```python
# Copy/paste
self.copy_to_clipboard("Hello World")
text = self.paste_from_clipboard()
# History
history = self.get_clipboard_history(limit=10)
```
### Window Management
```python
# EU window info
window = self.get_eu_window()
if window:
print(f"EU is at {window['rect']}")
# Check focus
if self.is_eu_focused():
# Safe to interact
pass
# Bring to front
self.bring_eu_to_front()
```
### Settings
```python
# Get global settings
theme = self.get_setting("theme", "dark")
opacity = self.get_setting("overlay_opacity", 0.9)
# Set global settings
self.set_setting("theme", "light")
```
### Logging
```python
self.log_debug("Debug info")
self.log_info("Something happened")
self.log_warning("Warning!")
self.log_error("Error occurred")
```
---
## Core Services
### Available Services
All services are accessed via `self.api` or convenience methods:
| Service | Access | Purpose |
|---------|--------|---------|
| OCR | `self.ocr_capture()` | Screen text recognition |
| Log Reader | `self.read_log()` | Read chat.log |
| Screenshot | `self.capture_screen()` | Screen capture |
| DataStore | `self.save_data()` | Persistent storage |
| Notifications | `self.notify()` | Toast notifications |
| Window Manager | `self.get_eu_window()` | EU window control |
| Audio | `self.play_sound()` | Sound playback |
| Clipboard | `self.copy_to_clipboard()` | Clipboard access |
| Task Manager | `self.run_in_background()` | Background tasks |
| Nexus API | `self.nexus_search()` | Entropia data |
| HTTP Client | `self.http_get()` | Web requests |
| Event Bus | `self.publish_typed()` | Event system |
---
## Event System
### Publishing Events
```python
from core.event_bus import LootEvent, SkillGainEvent
# Publish loot event
self.publish_typed(LootEvent(
mob_name="Argonaut",
items=[{"name": "Animal Oil", "value": 0.05}],
total_tt_value=0.05
))
# Publish skill gain
self.publish_typed(SkillGainEvent(
skill_name="Rifle",
gain_amount=5.2,
new_total=1500.0
))
```
### Subscribing to Events
```python
from core.event_bus import LootEvent, DamageEvent
# Subscribe to all loot events
self.subscribe_typed(LootEvent, self.on_loot)
# Subscribe with filters
self.subscribe_typed(
DamageEvent,
self.on_big_hit,
min_damage=100 # Only events with damage > 100
)
# Unsubscribe
self.unsubscribe_typed(subscription_id)
```
### Event Types
- `SkillGainEvent` - Skill improvements
- `LootEvent` - Loot received
- `DamageEvent` - Combat damage
- `GlobalEvent` - Globals/HOFs
- `ChatEvent` - Chat messages
- `EconomyEvent` - Economic events
- `SystemEvent` - System events
---
## UI Development
### EU Styling
Use the EU style constants:
```python
from core.eu_styles import EU_COLORS, EU_STYLES
# Colors
bg_color = EU_COLORS['bg_dark'] # #14131f
accent = EU_COLORS['accent_orange'] # #ff8c42
text = EU_COLORS['text_primary'] # #ffffff
# Apply to widget
widget.setStyleSheet(f"""
QWidget {{
background-color: {EU_COLORS['bg_dark']};
color: {EU_COLORS['text_primary']};
border: 1px solid {EU_COLORS['border_medium']};
}}
""")
```
### Best Practices
1. **Use layouts** - Never hardcode positions
2. **Thread safety** - Use `run_in_background()` for heavy work
3. **Responsive** - Handle window resizing
4. **Consistent** - Follow EU aesthetic
5. **Accessible** - Add tooltips, use clear labels
---
## Best Practices
### Do's ✓
- Use `save_data()`/`load_data()` for persistence
- Handle errors gracefully
- Use background tasks for heavy work
- Clean up in `shutdown()`
- Add docstrings to your methods
- Test on both Windows and Linux
### Don'ts ✗
- Don't block the main thread
- Don't use bare `except:` clauses
- Don't hardcode paths
- Don't ignore errors
- Don't forget to unsubscribe from events
---
## Examples
### Example 1: Simple Loot Tracker
```python
from plugins.base_plugin import BasePlugin
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
from core.event_bus import LootEvent
class SimpleLootTracker(BasePlugin):
name = "Simple Loot Tracker"
version = "1.0.0"
author = "Your Name"
description = "Track total loot value"
def initialize(self):
self.total_loot = self.load_data("total", 0.0)
self.subscribe_typed(LootEvent, self.on_loot)
def get_ui(self):
widget = QWidget()
layout = QVBoxLayout(widget)
self.label = QLabel(f"Total Loot: {self.total_loot:.2f} PED")
layout.addWidget(self.label)
return widget
def on_loot(self, event):
self.total_loot += event.total_tt_value
self.save_data("total", self.total_loot)
self.label.setText(f"Total Loot: {self.total_loot:.2f} PED")
def shutdown(self):
self.save_data("total", self.total_loot)
```
### Example 2: Calculator with OCR
```python
from plugins.base_plugin import BasePlugin
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel
class OcrCalculator(BasePlugin):
name = "OCR Calculator"
version = "1.0.0"
def get_ui(self):
widget = QWidget()
layout = QVBoxLayout(widget)
self.result_label = QLabel("Click Scan to read numbers")
layout.addWidget(self.result_label)
scan_btn = QPushButton("Scan Screen")
scan_btn.clicked.connect(self.scan_numbers)
layout.addWidget(scan_btn)
return widget
def scan_numbers(self):
result = self.ocr_capture()
text = result['text']
# Extract numbers and sum them
import re
numbers = [float(n) for n in re.findall(r'\d+\.?\d*', text)]
total = sum(numbers)
self.result_label.setText(f"Found: {numbers}\nTotal: {total}")
```
### Example 3: HTTP API Integration
```python
from plugins.base_plugin import BasePlugin
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit
class PriceChecker(BasePlugin):
name = "Price Checker"
version = "1.0.0"
def get_ui(self):
widget = QWidget()
layout = QVBoxLayout(widget)
self.text = QTextEdit()
self.text.setReadOnly(True)
layout.addWidget(self.text)
check_btn = QPushButton("Check Prices")
check_btn.clicked.connect(self.check_prices)
layout.addWidget(check_btn)
return widget
def check_prices(self):
# Fetch data from external API
response = self.http_get(
"https://api.example.com/prices",
cache_ttl=600 # Cache for 10 minutes
)
if response['status_code'] == 200:
prices = response['json']
self.text.setText(f"Current prices: {prices}")
else:
self.notify_error("Error", "Failed to fetch prices")
```
---
## Publishing
### Preparing for Release
1. **Test thoroughly**
- Test on Windows
- Test all features
- Check error handling
2. **Add documentation**
- README.md with description
- Screenshot of UI
- List of features
3. **Version properly**
- Use semantic versioning (1.0.0)
- Update version when making changes
4. **Submit to marketplace**
- Package your plugin
- Submit via Settings → Plugin Store
### Plugin Template
Use this template to start:
```python
"""
[Plugin Name] - [Brief description]
Author: [Your Name]
Version: 1.0.0
License: MIT
"""
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
from plugins.base_plugin import BasePlugin
class [PluginName]Plugin(BasePlugin):
"""
[Detailed description of your plugin]
Features:
- Feature 1
- Feature 2
- Feature 3
"""
# Metadata
name = "[Plugin Name]"
version = "1.0.0"
author = "[Your Name]"
description = "[Brief description]"
icon = "target" # Choose from assets/icons/
hotkey = None # Set to "ctrl+shift+x" for hotkey support
def initialize(self):
"""Initialize plugin. Called when plugin is loaded."""
# Load saved data
self.config = self.load_data("config", {
"enabled": True,
"setting1": "default"
})
# Subscribe to events if needed
# from core.event_bus import SomeEvent
# self.subscribe_typed(SomeEvent, self.on_event)
self.log_info(f"{self.name} initialized")
def get_ui(self):
"""Return the plugin's UI widget."""
widget = QWidget()
layout = QVBoxLayout(widget)
# Add your UI components here
label = QLabel(f"Welcome to {self.name}!")
layout.addWidget(label)
return widget
def on_hotkey(self):
"""Handle global hotkey press."""
self.notify_info("Hotkey", f"{self.name} hotkey pressed!")
def shutdown(self):
"""Cleanup when app closes."""
# Save data
self.save_data("config", self.config)
# Unsubscribe from events
# self.unsubscribe_all_typed()
self.log_info(f"{self.name} shutdown")
```
---
## Need Help?
- Check existing plugins in `plugins/` directory
- Read the full API documentation
- Join the community Discord
- Open an issue on GitHub
Happy plugin development! 🚀