# 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! 🚀