EU-Utility/docs/PLUGIN_DEVELOPMENT_GUIDE.md

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

  1. Quick Start
  2. Plugin Structure
  3. BasePlugin API Reference
  4. Core Services
  5. Event System
  6. UI Development
  7. Best Practices
  8. Examples
  9. Publishing

Quick Start

Creating Your First Plugin

  1. Create plugin directory:
mkdir plugins/my_plugin
touch plugins/my_plugin/__init__.py
touch plugins/my_plugin/plugin.py
  1. 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)
  1. 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 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:

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

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

  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:

"""
[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! 🚀