EU-Utility/docs/API_COOKBOOK.md

21 KiB

EU-Utility API Cookbook

Code recipes and examples for common tasks

Version: 2.1.0


Table of Contents

  1. Plugin Basics
  2. Working with Events
  3. Screen Capture & OCR
  4. Nexus API Recipes
  5. Data Persistence
  6. Background Tasks
  7. UI Components
  8. Advanced Patterns

Plugin Basics

Minimal Plugin Template

"""
Minimal working plugin template.
Copy this to get started quickly.
"""

from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
from plugins.base_plugin import BasePlugin


class MyPlugin(BasePlugin):
    """Description of what your plugin does."""
    
    # Required metadata
    name = "My Plugin"
    version = "1.0.0"
    author = "Your Name"
    description = "What my plugin does"
    
    # Optional
    hotkey = "ctrl+shift+x"  # Global hotkey
    icon = "target"          # Icon name
    
    def initialize(self):
        """Called when plugin is loaded."""
        self.data = self.load_data("my_key", default_value={})
        self.log_info(f"{self.name} initialized")
    
    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", f"{self.name} hotkey pressed!")
    
    def shutdown(self):
        """Called when app closes."""
        self.save_data("my_key", self.data)
        self.log_info(f"{self.name} shutdown")

Plugin with Settings

"""
Plugin with persistent settings.
"""

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, 
    QLabel, QLineEdit, QPushButton, QCheckBox
)
from plugins.base_plugin import BasePlugin


class ConfigurablePlugin(BasePlugin):
    """Plugin with user-configurable settings."""
    
    name = "Configurable Plugin"
    version = "1.0.0"
    author = "Your Name"
    description = "Plugin with settings example"
    
    def initialize(self):
        """Load settings with defaults."""
        self.settings = self.load_data("settings", {
            "username": "",
            "enabled": True,
            "threshold": 100
        })
    
    def get_ui(self):
        """Create settings UI."""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        
        # Username setting
        user_layout = QHBoxLayout()
        user_layout.addWidget(QLabel("Username:"))
        self.user_input = QLineEdit(self.settings["username"])
        self.user_input.textChanged.connect(self._on_user_changed)
        user_layout.addWidget(self.user_input)
        layout.addLayout(user_layout)
        
        # Enabled setting
        self.enabled_checkbox = QCheckBox("Enable feature")
        self.enabled_checkbox.setChecked(self.settings["enabled"])
        self.enabled_checkbox.toggled.connect(self._on_enabled_changed)
        layout.addWidget(self.enabled_checkbox)
        
        # Threshold setting
        threshold_layout = QHBoxLayout()
        threshold_layout.addWidget(QLabel("Threshold:"))
        self.threshold_input = QLineEdit(str(self.settings["threshold"]))
        self.threshold_input.textChanged.connect(self._on_threshold_changed)
        threshold_layout.addWidget(self.threshold_input)
        layout.addLayout(threshold_layout)
        
        # Save button
        save_btn = QPushButton("Save Settings")
        save_btn.clicked.connect(self._save_settings)
        layout.addWidget(save_btn)
        
        layout.addStretch()
        return widget
    
    def _on_user_changed(self, text):
        self.settings["username"] = text
    
    def _on_enabled_changed(self, checked):
        self.settings["enabled"] = checked
    
    def _on_threshold_changed(self, text):
        try:
            self.settings["threshold"] = int(text)
        except ValueError:
            pass
    
    def _save_settings(self):
        self.save_data("settings", self.settings)
        self.notify_success("Settings Saved", "Your settings have been saved.")
    
    def shutdown(self):
        self.save_data("settings", self.settings)

Working with Events

Subscribe to Loot Events

def initialize(self):
    """Subscribe to loot events."""
    from core.event_bus import LootEvent
    
    self.loot_subscription = self.subscribe_typed(
        LootEvent,
        self.on_loot_received,
        replay_last=10  # Get last 10 events immediately
    )

def on_loot_received(self, event):
    """Handle loot event."""
    print(f"Received loot from {event.mob_name}")
    print(f"Items: {event.items}")
    print(f"Total TT: {event.total_tt_value}")
    
    # Update UI
    self.total_loot += event.total_tt_value
    self.update_display()

def shutdown(self):
    """Unsubscribe on shutdown."""
    self.unsubscribe_typed(self.loot_subscription)

Subscribe to Skill Gains

def initialize(self):
    """Subscribe to skill gain events."""
    from core.event_bus import SkillGainEvent
    
    # Subscribe to specific skills
    self.subscribe_typed(
        SkillGainEvent,
        self.on_skill_gain,
        skill_names=["Rifle", "Pistol", "Melee"]
    )

def on_skill_gain(self, event):
    """Handle skill gain."""
    print(f"{event.skill_name} increased by {event.gain_amount}")
    print(f"New value: {event.skill_value}")

Subscribe to Damage Events

def initialize(self):
    """Subscribe to high damage hits."""
    from core.event_bus import DamageEvent
    
    # Only events with damage > 100
    self.subscribe_typed(
        DamageEvent,
        self.on_big_hit,
        min_damage=100
    )

def on_big_hit(self, event):
    """Handle big damage hit."""
    print(f"Big hit! {event.damage_amount} damage")
    print(f"Critical: {event.is_critical}")
    print(f"Target: {event.target_name}")

Publish Custom Events

def track_my_event(self, data):
    """Publish a custom event."""
    from core.event_bus import SystemEvent
    
    self.publish_typed(SystemEvent(
        message=f"Custom event: {data}",
        severity="info"
    ))

Screen Capture & OCR

Capture Screen Region

def capture_game_window(self):
    """Capture the game window area."""
    # Get EU window info
    window = self.get_eu_window()
    if not window:
        return None
    
    # Capture specific region
    screenshot = self.capture_region(
        x=window['x'] + 100,
        y=window['y'] + 50,
        width=400,
        height=300
    )
    
    return screenshot

OCR Text from Screen

def read_game_text(self):
    """Read text from game screen using OCR."""
    # Capture and OCR
    result = self.ocr_capture(region=(100, 100, 400, 300))
    
    if result:
        text = result['text']
        confidence = result['confidence']
        
        print(f"OCR Result: {text}")
        print(f"Confidence: {confidence}")
        
        return text
    
    return None

OCR with Post-Processing

import re

def read_coordinates(self):
    """Read coordinates from game UI."""
    result = self.ocr_capture()
    
    if not result:
        return None
    
    text = result['text']
    
    # Extract numbers (coordinates)
    numbers = re.findall(r'\d+', text)
    
    if len(numbers) >= 2:
        return {
            'x': int(numbers[0]),
            'y': int(numbers[1])
        }
    
    return None

Nexus API Recipes

Search for Items

def search_weapons(self, query):
    """Search for weapons in Nexus API."""
    results = self.nexus_search(
        query=query,
        entity_type="weapons",
        limit=20
    )
    
    for item in results:
        print(f"{item['name']} ({item['type']})")
    
    return results

Get Item Details

def get_weapon_stats(self, item_id):
    """Get detailed weapon information."""
    details = self.nexus_get_item_details(item_id)
    
    if details:
        print(f"Name: {details['name']}")
        print(f"Damage: {details.get('damage', 'N/A')}")
        print(f"Range: {details.get('range', 'N/A')}m")
        print(f"TT Value: {details.get('tt_value', 'N/A')} PED")
    
    return details

Get Market Data

def check_market_price(self, item_id):
    """Check current market price."""
    market = self.nexus_get_market_data(item_id)
    
    if market:
        markup = market.get('current_markup')
        volume = market.get('volume_24h')
        
        print(f"Current markup: {markup}%")
        print(f"24h volume: {volume}")
        
        return market
    
    return None

Batch Operations

def analyze_items(self, item_ids):
    """Analyze multiple items efficiently."""
    results = {}
    
    for item_id in item_ids:
        details = self.nexus_get_item_details(item_id)
        market = self.nexus_get_market_data(item_id)
        
        results[item_id] = {
            'details': details,
            'market': market
        }
    
    return results

Data Persistence

Save/Load Plugin Data

def save_session(self, session_data):
    """Save hunting session data."""
    self.save_data("current_session", {
        'start_time': session_data['start'],
        'total_loot': session_data['loot'],
        'mobs_killed': session_data['kills'],
        'items': session_data['items']
    })

def load_session(self):
    """Load hunting session data."""
    return self.load_data("current_session", {
        'start_time': None,
        'total_loot': 0,
        'mobs_killed': 0,
        'items': []
    })

Manage Multiple Data Keys

def initialize(self):
    """Initialize with multiple data keys."""
    # Settings
    self.config = self.load_data("config", self.default_config())
    
    # Statistics
    self.stats = self.load_data("stats", self.default_stats())
    
    # History
    self.history = self.load_data("history", [])

def default_config(self):
    return {
        'enabled': True,
        'volume': 1.0,
        'theme': 'dark'
    }

def default_stats(self):
    return {
        'total_sessions': 0,
        'total_loot': 0,
        'playtime_hours': 0
    }

def shutdown(self):
    """Save all data on shutdown."""
    self.save_data("config", self.config)
    self.save_data("stats", self.stats)
    self.save_data("history", self.history)

Background Tasks

Simple Background Task

def start_background_work(self):
    """Run function in background."""
    def heavy_computation(data):
        # This runs in a background thread
        result = process_large_dataset(data)
        return result
    
    task_id = self.run_in_background(
        heavy_computation,
        self.large_dataset,
        priority='normal',
        on_complete=self.on_work_done,
        on_error=self.on_work_error
    )

def on_work_done(self, result):
    """Called when background work completes."""
    self.result_label.setText(f"Done: {result}")

def on_work_error(self, error):
    """Called on error."""
    self.status_label.setText(f"Error: {error}")

Periodic Background Task

def initialize(self):
    """Start periodic data refresh."""
    # Refresh every 30 seconds
    self.schedule_task(
        delay_ms=0,           # Start immediately
        func=self.refresh_data,
        periodic=True,        # Repeat
        interval_ms=30000,    # Every 30 seconds
        on_complete=self.update_display
    )

def refresh_data(self):
    """Fetch fresh data."""
    # This runs periodically in background
    return fetch_from_api()

Cancelable Task

def start_long_operation(self):
    """Start operation that can be cancelled."""
    self.current_task = self.run_in_background(
        self.long_running_task,
        on_complete=self.on_complete
    )
    
    # Enable cancel button
    self.cancel_btn.setEnabled(True)

def cancel_operation(self):
    """Cancel the running operation."""
    if hasattr(self, 'current_task'):
        self.cancel_task(self.current_task)
        self.status_label.setText("Cancelled")

UI Components

Styled Card Widget

from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel

def create_card(self, title, content):
    """Create a styled card widget."""
    card = QFrame()
    card.setStyleSheet("""
        QFrame {
            background-color: #2a2a2a;
            border: 1px solid #444;
            border-radius: 8px;
            padding: 10px;
        }
    """)
    
    layout = QVBoxLayout(card)
    
    title_label = QLabel(title)
    title_label.setStyleSheet("font-weight: bold; color: #4a9eff;")
    layout.addWidget(title_label)
    
    content_label = QLabel(content)
    content_label.setStyleSheet("color: rgba(255,255,255,200);")
    content_label.setWordWrap(True)
    layout.addWidget(content_label)
    
    return card

Progress Widget

from PyQt6.QtWidgets import QProgressBar, QLabel, QVBoxLayout, QWidget

def create_progress_widget(self):
    """Create a progress display widget."""
    widget = QWidget()
    layout = QVBoxLayout(widget)
    
    self.progress_label = QLabel("Ready")
    layout.addWidget(self.progress_label)
    
    self.progress_bar = QProgressBar()
    self.progress_bar.setStyleSheet("""
        QProgressBar {
            background-color: #333;
            border-radius: 4px;
            height: 8px;
        }
        QProgressBar::chunk {
            background-color: #4a9eff;
            border-radius: 4px;
        }
    """)
    layout.addWidget(self.progress_bar)
    
    return widget

def update_progress(self, value, message):
    """Update progress display."""
    self.progress_bar.setValue(value)
    self.progress_label.setText(message)

Data Table

from PyQt6.QtWidgets import QTableWidget, QTableWidgetItem

def create_data_table(self, headers):
    """Create a data table."""
    table = QTableWidget()
    table.setColumnCount(len(headers))
    table.setHorizontalHeaderLabels(headers)
    table.horizontalHeader().setStretchLastSection(True)
    table.setStyleSheet("""
        QTableWidget {
            background-color: #2a2a2a;
            border: 1px solid #444;
        }
        QHeaderView::section {
            background-color: #333;
            color: white;
            padding: 5px;
        }
    """)
    return table

def populate_table(self, table, data):
    """Populate table with data."""
    table.setRowCount(len(data))
    
    for row, item in enumerate(data):
        for col, value in enumerate(item):
            table.setItem(row, col, QTableWidgetItem(str(value)))

Advanced Patterns

Singleton Pattern

class MyService:
    """Singleton service example."""
    
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance
    
    def __init__(self):
        if self._initialized:
            return
        self._initialized = True
        # Initialize here

Plugin Communication

def register_api_endpoint(self):
    """Register an API for other plugins."""
    from core.plugin_api import APIEndpoint, APIType
    
    endpoint = APIEndpoint(
        name="get_my_data",
        api_type=APIType.CUSTOM,
        description="Get data from this plugin",
        handler=self.provide_data,
        plugin_id=self._plugin_id,
        version="1.0.0"
    )
    
    self.register_api(endpoint)

def use_other_plugin_api(self):
    """Call API from another plugin."""
    result = self.call_api("other_plugin", "their_api", arg1, arg2)
    return result

Error Handling Pattern

def safe_operation(self):
    """Operation with proper error handling."""
    try:
        result = self.risky_operation()
        return result
    except NetworkError as e:
        self.log_warning(f"Network error: {e}")
        self.notify_warning("Connection Issue", "Please check your internet connection.")
    except ValueError as e:
        self.log_error(f"Invalid value: {e}")
        self.notify_error("Error", f"Invalid input: {e}")
    except Exception as e:
        self.log_exception(f"Unexpected error: {e}")
        self.notify_error("Error", "An unexpected error occurred.")
    
    return None

Context Manager for Resources

from contextlib import contextmanager

@contextmanager
def temp_file(self, suffix='.tmp'):
    """Context manager for temporary files."""
    import tempfile
    
    fd, path = tempfile.mkstemp(suffix=suffix)
    try:
        os.close(fd)
        yield path
    finally:
        if os.path.exists(path):
            os.remove(path)

def process_with_temp(self, data):
    """Use temporary file safely."""
    with self.temp_file() as temp_path:
        with open(temp_path, 'w') as f:
            f.write(data)
        # Process temp file
        result = self.process_file(temp_path)
    # File automatically cleaned up
    return result

Complete Example: Mini Plugin

"""
MiniTracker - Complete plugin example
Tracks a simple counter with persistent storage.
"""

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QPushButton, QSpinBox
)
from plugins.base_plugin import BasePlugin


class MiniTrackerPlugin(BasePlugin):
    """Simple counter tracker example."""
    
    name = "Mini Tracker"
    version = "1.0.0"
    author = "Example"
    description = "Simple counter demonstration"
    hotkey = "ctrl+shift+m"
    
    def initialize(self):
        """Load saved state."""
        self.count = self.load_data("count", 0)
        self.increment = self.load_data("increment", 1)
        self.log_info(f"Loaded count: {self.count}")
    
    def get_ui(self):
        """Create UI."""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setSpacing(15)
        
        # Counter display
        self.counter_label = QLabel(str(self.count))
        self.counter_label.setStyleSheet("""
            font-size: 48px;
            font-weight: bold;
            color: #4a9eff;
        """)
        self.counter_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.counter_label)
        
        # Controls
        btn_layout = QHBoxLayout()
        
        decrement_btn = QPushButton("-")
        decrement_btn.clicked.connect(self.decrement)
        btn_layout.addWidget(decrement_btn)
        
        increment_btn = QPushButton("+")
        increment_btn.clicked.connect(self.increment_count)
        btn_layout.addWidget(increment_btn)
        
        layout.addLayout(btn_layout)
        
        # Increment setting
        setting_layout = QHBoxLayout()
        setting_layout.addWidget(QLabel("Increment by:"))
        
        self.increment_spin = QSpinBox()
        self.increment_spin.setRange(1, 100)
        self.increment_spin.setValue(self.increment)
        self.increment_spin.valueChanged.connect(self._on_increment_changed)
        setting_layout.addWidget(self.increment_spin)
        
        layout.addLayout(setting_layout)
        
        # Reset button
        reset_btn = QPushButton("Reset Counter")
        reset_btn.clicked.connect(self.reset)
        layout.addWidget(reset_btn)
        
        layout.addStretch()
        return widget
    
    def increment_count(self):
        """Increment counter."""
        self.count += self.increment
        self._update_display()
        self.save_data("count", self.count)
        self.record_event("counter_incremented", {"value": self.count})
    
    def decrement(self):
        """Decrement counter."""
        self.count -= self.increment
        self._update_display()
        self.save_data("count", self.count)
    
    def reset(self):
        """Reset counter."""
        self.count = 0
        self._update_display()
        self.save_data("count", self.count)
        self.notify_info("Reset", "Counter has been reset.")
    
    def _on_increment_changed(self, value):
        """Handle increment change."""
        self.increment = value
        self.save_data("increment", value)
    
    def _update_display(self):
        """Update counter display."""
        self.counter_label.setText(str(self.count))
    
    def on_hotkey(self):
        """Handle hotkey."""
        self.increment_count()
        self.notify_info("Mini Tracker", f"Count: {self.count}")
    
    def shutdown(self):
        """Save on shutdown."""
        self.save_data("count", self.count)
        self.save_data("increment", self.increment)

Quick Reference

Common Tasks

Task Code
Log message self.log_info("message")
Notify user self.notify_info("Title", "Message")
Save data self.save_data("key", value)
Load data self.load_data("key", default)
Run in background self.run_in_background(func, on_complete=cb)
Schedule task self.schedule_task(delay_ms, func, periodic=True)
Get EU window self.get_eu_window()
Capture screen self.ocr_capture(region=(x,y,w,h))
Search Nexus self.nexus_search("query", "items")
Play sound self.play_sound("hof")

Happy plugin development! 🚀