14 KiB
14 KiB
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
- Quick Start
- Plugin Structure
- BasePlugin API Reference
- Core Services
- Event System
- UI Development
- Best Practices
- Examples
- Publishing
Quick Start
Creating Your First Plugin
- Create plugin directory:
mkdir plugins/my_plugin
touch plugins/my_plugin/__init__.py
touch plugins/my_plugin/plugin.py
- Basic plugin structure:
# 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)
- 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
# 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
# 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
# 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
# Read recent log lines
lines = self.read_log(lines=100)
# Filtered reading
damage_lines = self.read_log(lines=50, filter_text="damage")
Nexus API
# 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
# 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
# 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
# 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
# Copy/paste
self.copy_to_clipboard("Hello World")
text = self.paste_from_clipboard()
# History
history = self.get_clipboard_history(limit=10)
Window Management
# 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
# 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
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
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
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 improvementsLootEvent- Loot receivedDamageEvent- Combat damageGlobalEvent- Globals/HOFsChatEvent- Chat messagesEconomyEvent- Economic eventsSystemEvent- System events
UI Development
EU Styling
Use the EU style constants:
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
- Use layouts - Never hardcode positions
- Thread safety - Use
run_in_background()for heavy work - Responsive - Handle window resizing
- Consistent - Follow EU aesthetic
- 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
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
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
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
-
Test thoroughly
- Test on Windows
- Test all features
- Check error handling
-
Add documentation
- README.md with description
- Screenshot of UI
- List of features
-
Version properly
- Use semantic versioning (1.0.0)
- Update version when making changes
-
Submit to marketplace
- Package your plugin
- Submit via Settings → Plugin Store
Plugin Template
Use this template to start:
"""
[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! 🚀