# EU-Utility API Cookbook > Code recipes and examples for common tasks > > **Version:** 2.1.0 --- ## Table of Contents 1. [Plugin Basics](#plugin-basics) 2. [Working with Events](#working-with-events) 3. [Screen Capture & OCR](#screen-capture--ocr) 4. [Nexus API Recipes](#nexus-api-recipes) 5. [Data Persistence](#data-persistence) 6. [Background Tasks](#background-tasks) 7. [UI Components](#ui-components) 8. [Advanced Patterns](#advanced-patterns) --- ## Plugin Basics ### Minimal Plugin Template ```python """ 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 ```python """ 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python """ 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! 🚀