21 KiB
21 KiB
EU-Utility API Cookbook
Code recipes and examples for common tasks
Version: 2.1.0
Table of Contents
- Plugin Basics
- Working with Events
- Screen Capture & OCR
- Nexus API Recipes
- Data Persistence
- Background Tasks
- UI Components
- 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! 🚀