EU-Utility-V3/docs/PLUGIN_GUIDE.md

7.0 KiB

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

{
  "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

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<boolean> {
    this.context = context;
    
    // Subscribe to events
    await this.context.api.subscribeEvent('loot.received', (event) => {
      this.onLoot(event);
    });
    
    return true;
  }
  
  async activate(): Promise<boolean> {
    this.context.logger.info('My Plugin activated');
    return true;
  }
  
  async deactivate(): Promise<void> {
    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 = `
      <label>Enable Feature</label>
      <input type="checkbox" id="feature-toggle" />
    `;
    return div;
  }
}

BasePlugin Class

abstract class BasePlugin {
  abstract id: string;
  abstract name: string;
  abstract version: string;
  
  // Required methods
  abstract initialize(context: PluginContext): Promise<boolean>;
  abstract activate(): Promise<boolean>;
  abstract deactivate(): Promise<void>;
  
  // 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:

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<boolean> {
    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<boolean> {
    this.sessionStart = Date.now();
    this.context.logger.info('Loot Tracker: Session started');
    
    // Create overlay widget
    await this.createWidget();
    
    return true;
  }
  
  async deactivate(): Promise<void> {
    // 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 `
      <div style="font-family: system-ui; color: #fff; padding: 12px;">
        <div style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">
          Session: ${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}
        </div>
        <div style="font-size: 24px; font-weight: 700; color: #10b981;">
          ${this.totalLoot.toFixed(2)} PED
        </div>
        <div style="font-size: 12px; color: #94a3b8; margin-top: 4px;">
          ${this.mobCount} mobs killed
        </div>
      </div>
    `;
  }
  
  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

# 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