# Plugin Development Guide Create plugins for EU-Utility V3. --- ## Quick Start ### 1. Create Plugin Structure ``` my-plugin/ plugin.json # Plugin manifest index.ts # Main plugin code README.md # Documentation ``` ### 2. Write Manifest ```json { "manifest_version": "3.0", "plugin": { "id": "com.example.myplugin", "name": "My Plugin", "version": "1.0.0", "author": "Your Name", "description": "What your plugin does", "category": "utility" }, "permissions": [ "log_reader:read", "overlay:create", "data_store:write" ], "ui": { "has_overlay": true, "has_settings": true, "hotkey": "ctrl+shift+m" } } ``` ### 3. Write Plugin Code ```typescript import { BasePlugin, PluginContext } from 'eu-utility-plugin-sdk'; export default class MyPlugin extends BasePlugin { id = 'com.example.myplugin'; name = 'My Plugin'; version = '1.0.0'; private context: PluginContext; private lootCount = 0; async initialize(context: PluginContext): Promise { this.context = context; // Subscribe to events await this.context.api.subscribeEvent('loot.received', (event) => { this.onLoot(event); }); return true; } async activate(): Promise { this.context.logger.info('My Plugin activated'); return true; } async deactivate(): Promise { this.context.logger.info('My Plugin deactivated'); } private onLoot(event: any) { this.lootCount++; // Show notification this.context.api.showNotification({ title: 'Loot Tracked', body: `Total loots: ${this.lootCount}` }); // Save data this.context.api.savePluginData( this.id, 'stats', { lootCount: this.lootCount } ); } getSettingsUI(): HTMLElement { // Return settings UI element const div = document.createElement('div'); div.innerHTML = ` `; return div; } } ``` --- ## BasePlugin Class ```typescript abstract class BasePlugin { abstract id: string; abstract name: string; abstract version: string; // Required methods abstract initialize(context: PluginContext): Promise; abstract activate(): Promise; abstract deactivate(): Promise; // Optional methods getOverlayUI?(): HTMLElement; getSettingsUI?(): HTMLElement; onHotkey?(hotkey: string): void; } ``` --- ## Permissions Available permissions: | Permission | Description | |------------|-------------| | `log_reader:read` | Read game logs | | `overlay:create` | Create overlay widgets | | `data_store:write` | Save plugin data | | `data_store:read` | Read plugin data | | `network:external` | Make HTTP requests | | `ocr:read` | Use OCR service | | `clipboard:read` | Read clipboard | | `clipboard:write` | Write to clipboard | | `notification:send` | Send notifications | --- ## Example: Loot Tracker Complete example plugin: ```typescript import { BasePlugin, PluginContext } from 'eu-utility-plugin-sdk'; export default class LootTracker extends BasePlugin { id = 'eu.utility.loot-tracker'; name = 'Loot Tracker'; version = '2.0.0'; author = 'Aether'; description = 'Track hunting loot with ROI analysis'; category = 'tracking'; private context: PluginContext; private sessionStart = Date.now(); private totalLoot = 0; private mobCount = 0; async initialize(context: PluginContext): Promise { this.context = context; // Load saved data const saved = await context.api.loadPluginData(this.id, 'session'); if (saved) { this.totalLoot = saved.totalLoot || 0; this.mobCount = saved.mobCount || 0; } // Subscribe to loot events await context.api.subscribeEvent('loot.received', (e) => { this.handleLoot(e.data); }); return true; } async activate(): Promise { this.sessionStart = Date.now(); this.context.logger.info('Loot Tracker: Session started'); // Create overlay widget await this.createWidget(); return true; } async deactivate(): Promise { // Save session data await this.context.api.savePluginData(this.id, 'session', { totalLoot: this.totalLoot, mobCount: this.mobCount, duration: Date.now() - this.sessionStart }); } private async handleLoot(data: LootData) { this.totalLoot += data.totalTT; this.mobCount++; // Update widget await this.updateWidget(); // Notify on big loot if (data.totalTT > 50) { await this.context.api.showNotification({ title: 'Big Loot!', body: `${data.mobName}: ${data.totalTT.toFixed(2)} PED`, sound: true }); } // Log to file await this.logLoot(data); } private async createWidget() { await this.context.widget.create({ id: 'loot-tracker-main', title: 'Loot Tracker', width: 280, height: 150, x: 50, y: 50, content: this.renderWidget() }); } private renderWidget(): string { const elapsed = Math.floor((Date.now() - this.sessionStart) / 1000); const hours = Math.floor(elapsed / 3600); const mins = Math.floor((elapsed % 3600) / 60); const secs = elapsed % 60; return `
Session: ${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}
${this.totalLoot.toFixed(2)} PED
${this.mobCount} mobs killed
`; } private async updateWidget() { await this.context.widget.update('loot-tracker-main', { content: this.renderWidget() }); } private async logLoot(data: LootData) { const entry = { timestamp: new Date().toISOString(), mob: data.mobName, tt: data.totalTT, items: data.items.length }; const logs = await this.context.api.loadPluginData(this.id, 'logs') || []; logs.push(entry); // Keep last 1000 entries if (logs.length > 1000) logs.shift(); await this.context.api.savePluginData(this.id, 'logs', logs); } onHotkey(hotkey: string): void { if (hotkey === 'ctrl+shift+l') { this.context.api.showNotification({ title: 'Loot Stats', body: `Total: ${this.totalLoot.toFixed(2)} PED in ${this.mobCount} kills` }); } } } interface LootData { mobName: string; totalTT: number; items: any[]; } ``` --- ## Building Plugins ```bash # Install SDK npm install eu-utility-plugin-sdk # Build npm run build # Package eu-util plugin package ``` --- ## Publishing 1. Create release on Git 2. Tag with version 3. Attach plugin.zip --- *Plugin Development Guide - EU-Utility V3*