🛒 Dragon Lord Plugin Marketplace

📚 Plugin Creation Guide

Learn how to create powerful plugins for DragonLord

Introduction

DragonLord's plugin system allows you to extend the game with custom features, locations, quests, and more. Plugins are modular, secure, and easy to distribute.

💡 Key Features:
  • Extend game locations with new services
  • Create custom quest packs
  • Add new icon/asset packs
  • Implement custom game mechanics
  • Secure sandboxed execution
  • Version compatibility checking

Getting Started

Prerequisites

Plugin Requirements

Plugin Structure

Every plugin must follow a specific folder structure. Here's the standard layout:

your-plugin-id/ plugin.json ← Required: Plugin manifest hooks/ ← Hook handlers (optional) location_services.php service_action.php assets/ ← Icons, images, etc. (optional) icon.png header.jpg src/ ← PHP classes (optional) MyPluginClass.php README.md ← Documentation (recommended)
⚠️ Important: When creating your ZIP file, ensure the plugin folder structure is preserved. The ZIP should contain either:
  • The plugin files directly in the root (plugin.json at root level)
  • Or a single folder containing all plugin files

Plugin Manifest (plugin.json)

The manifest file defines your plugin's metadata, requirements, hooks, and permissions.

Required Fields

Field Type Description
id string Unique plugin identifier (lowercase, hyphens only)
name string Display name of the plugin
version string Semantic version (e.g., "1.0.0")
type string Plugin type (see Plugin Types section)

Optional Fields - Detailed

Field Type Description
author string Plugin author name (displayed in marketplace and admin portal)
description string Detailed plugin description (supports markdown formatting)
game_version object Version compatibility requirements (see Version Compatibility below)
requires object Dependencies and requirements (see Dependencies below)
hooks object Hook handlers mapping (see Hooks System section)
permissions object Plugin permissions (see Permissions section)
config object Configuration schema (see Configuration below)
target_location string For location_extension type: which location to extend (e.g., "town", "forest")
icon string Path to plugin icon (displayed in admin portal and marketplace)

Version Compatibility

The game_version object ensures your plugin works with compatible game versions:

"game_version": {
  "min": "1.0.0",        // Minimum game version required
  "max": "2.0.0",        // Maximum game version supported (optional)
  "api_version": "1.0"   // Required plugin API version
}

Dependencies

The requires object allows your plugin to depend on other plugins:

"requires": {
  "dependencies": [
    "another-plugin-id",
    "yet-another-plugin"
  ]
}

Configuration Schema

The config object defines settings that admins can configure in the admin portal:

"config": {
  "api_key": {
    "type": "string",
    "required": true,
    "label": "API Key",
    "description": "Your API key for external service",
    "secret": true,
    "default": ""
  },
  "enabled": {
    "type": "boolean",
    "default": true,
    "label": "Enable Feature",
    "description": "Turn this feature on or off"
  },
  "max_items": {
    "type": "number",
    "default": 10,
    "min": 1,
    "max": 100,
    "label": "Maximum Items",
    "description": "Maximum number of items allowed"
  },
  "theme_color": {
    "type": "string",
    "default": "#667eea",
    "label": "Theme Color",
    "description": "Color theme for the plugin UI"
  }
}

Configuration Field Options

Option Type Description
type string Field type: "string", "number", "boolean", "select"
required boolean Whether the field must be filled (default: false)
default any Default value if not provided
label string Display label in admin UI
description string Help text shown below the field
secret boolean For strings: mask input (for passwords, API keys)
min number For numbers: minimum value
max number For numbers: maximum value
options array For select type: array of {value, label} objects
💡 Accessing Configuration: In your hook files, use PluginManager::getInstance()->getPluginConfig($pluginId) to retrieve saved configuration values.

Example Manifest

{
  "id": "my-awesome-plugin",
  "name": "My Awesome Plugin",
  "version": "1.0.0",
  "author": "Your Name",
  "description": "Adds awesome features to the game",
  "type": "location_extension",
  "target_location": "town",
  "game_version": {
    "min": "1.0.0",
    "api_version": "1.0"
  },
  "requires": {
    "dependencies": []
  },
  "hooks": {
    "location_services_init": "hooks/location_services.php",
    "location_service_action": "hooks/service_action.php"
  },
  "permissions": {
    "execute_hooks": ["location_services_init", "location_service_action"]
  },
  "config": {
    "setting1": {
      "type": "string",
      "default": "value",
      "description": "Setting description"
    }
  }
}

Plugin Types

Location Extension

Adds new services to existing game locations.

{
  "type": "location_extension",
  "target_location": "town"
}

Quest Pack

Adds new quests to the game.

{
  "type": "quest_pack"
}

Asset Pack

Provides new icons, images, or other assets.

{
  "type": "asset_pack"
}

Hooks System

Hooks allow plugins to interact with the game at specific points. Register hooks in your manifest:

"hooks": {
  "location_services_init": "hooks/location_services.php",
  "location_service_action": "hooks/service_action.php"
}

Available Hooks

Hook Name Purpose Data Passed
location_services_init Add services to a location Location key, existing services
location_service_action Handle service actions Location, service, action, player_id, params

Hook Handler Example

<?php
/**
 * Location Services Hook
 * Adds a library service to town
 */

// $data contains location info and existing services
$locationKey = $data['location_key'] ?? '';

if ($locationKey === 'town') {
    // Add library service
    $data['services']['library'] = [
        'name' => 'Library',
        'description' => 'Study ancient tomes',
        'icon' => 'book'
    ];
}

return $data;
?>
💡 Tip: Hook handlers must return the modified $data array. For service actions, set $data['handled'] = true and $data['result'] to indicate the action was processed.

Service Action Response Format

When handling service actions, your hook must return a properly formatted response:

// For 'view' action - show modal with actions
$data['handled'] = true;
$data['result'] = [
    'success' => true,
    'service' => 'library',
    'name' => 'Library',
    'description' => 'Study ancient tomes...',
    'modal_config' => [...],
    'actions' => [...]
];

// For action execution (e.g., 'research', 'study')
$data['handled'] = true;
$data['result'] = [
    'success' => true,
    'message' => 'You gained 25 experience!',
    'player' => [
        'gold' => $updatedGold,
        'experience' => $updatedExp
    ],
    'exp_gain' => 25,
    'gold_spent' => 10
];

// For errors
$data['handled'] = true;
$data['result'] = [
    'success' => false,
    'message' => 'You need 10 gold to study.'
];

Response Fields

Field Type Required Description
success boolean Yes Whether the action succeeded
message string No Message shown to player (toast notification)
player object No Updated player stats (gold, experience, etc.)
service string For 'view' Service identifier
name string For 'view' Service display name
description string For 'view' Service description
modal_config object No Modal customization options
actions object For 'view' Available actions for the service

Permissions

Plugins must declare permissions for security. The sandbox system enforces these restrictions.

Permission Types - Detailed

1. execute_hooks

Lists which hooks your plugin is allowed to execute. This is the most common permission type.

"permissions": {
  "execute_hooks": [
    "location_services_init",
    "location_service_action"
  ]
}

2. file_system

Controls file system access for reading and writing files.

"permissions": {
  "file_system": {
    "read": [
      "/plugins/{plugin_id}/assets",
      "/public/icons"
    ],
    "write": [
      "/plugins/{plugin_id}/cache"
    ]
  }
}

3. database

Controls database table access for reading and writing data.

"permissions": {
  "database": {
    "read": ["players", "items"],
    "write": ["custom_plugin_table"]
  }
}
⚠️ Security Best Practices:
  • Only request the minimum permissions needed
  • Avoid requesting write access to core game tables unless absolutely necessary
  • Use plugin-specific tables or cache directories when possible
  • Plugins with excessive permissions may be rejected during review

Example Permissions

"permissions": {
  "execute_hooks": [
    "location_services_init",
    "location_service_action"
  ],
  "file_system": {
    "read": ["/plugins/{plugin_id}/assets"],
    "write": []
  },
  "database": {
    "read": ["players"],
    "write": []
  }
}
⚠️ Security: Only request the minimum permissions needed. Plugins with excessive permissions may be rejected.

Assets & Icons

Plugins can include custom icons, images, and other assets. Place them in the assets/ directory.

Folder Structure

your-plugin-id/ assets/ icon.png ← Service icon (48x48px) header.jpg ← Header image action-icon.png ← Action icon (32x32px)

Using Assets in Hooks

Reference assets using relative paths from the plugin directory:

$data['result'] = [
    'success' => true,
    'modal_config' => [
        'icon' => 'assets/icon.png',
        'image' => 'assets/header.jpg'
    ],
    'actions' => [
        'action1' => [
            'name' => 'Action',
            'icon' => 'assets/action-icon.png'
        ]
    ]
];

Asset Path Resolution

Asset paths are resolved in the following priority order:

  1. Full URLs: If path starts with http:// or https://, used as-is
    'icon' => 'https://example.com/my-icon.png'
  2. Absolute paths: If path starts with /, relative to game base path
    'icon' => '/public/icons/custom.png'
  3. Relative paths: Otherwise, resolved as plugins/{plugin-id}/assets/{path}
    'icon' => 'assets/library-icon.png'
    // Resolves to: plugins/location-extension-library/assets/library-icon.png

Modal Customization Options

When returning service data, you can customize the modal appearance:

$data['result'] = [
    'success' => true,
    'service' => 'library',
    'name' => 'Library',
    'description' => 'Study ancient tomes and research knowledge.',
    'modal_config' => [
        // Service icon (48x48px recommended)
        'icon' => 'assets/library-icon.png',
        
        // Header background image (optional)
        'image' => 'assets/library-header.jpg',
        
        // Custom header text color
        'header_color' => '#8B4513',
        
        // Modal width
        'max_width' => '700px',
        
        // Show/hide player gold display
        'show_gold' => true,
        
        // Custom HTML content (injected before actions)
        'custom_html' => '<div class="custom-stats">...</div>'
    ],
    'actions' => [
        'research' => [
            'name' => 'Research',
            'description' => 'Spend 10 gold to gain 25 experience',
            'cost' => 10,
            'icon' => 'assets/research-icon.png',
            // Optional: fully custom HTML for this action
            // 'custom_html' => '<div>Custom action HTML</div>'
        ]
    ]
];

Modal Config Options Reference

Option Type Default Description
icon string none Service icon path (48x48px recommended)
image string none Header background image path
header_color string "#ffaa00" CSS color for service title
max_width string "600px" Modal maximum width (CSS value)
show_gold boolean true Show player gold display
custom_html string none HTML injected before actions list

Action Options Reference

Option Type Required Description
name string Yes Display name for the action
description string No Description text shown below name
cost number No Gold cost (0 for free actions)
icon string No Action icon path (32x32px recommended)
custom_html string No Fully custom HTML (overrides default rendering)
✅ Best Practices:
  • Use PNG for icons (transparency support)
  • Optimize images (keep file sizes reasonable)
  • Recommended sizes: Service icons 48x48px, Action icons 32x32px
  • Include all assets in your ZIP file

Complete Examples

Location Extension Plugin

See the example plugin: plugins/location-extension-library/

This plugin adds a Library service to Town with research actions.

Complete Location Extension Example

{
  "id": "location-extension-library",
  "name": "Town Library Extension",
  "version": "1.0.2",
  "author": "DragonLord Team",
  "description": "Adds a Library service to Town with research actions",
  "type": "location_extension",
  "target_location": "town",
  "game_version": {
    "min": "0.9.0",
    "api_version": "1.0"
  },
  "permissions": {
    "execute_hooks": [
      "location_services_init",
      "location_service_action"
    ]
  },
  "hooks": {
    "location_services_init": "hooks/location_services.php",
    "location_service_action": "hooks/service_action.php"
  }
}

Advanced Hook Example

Here's a complete service action handler with error handling and player updates:

<?php
/**
 * Service Action Hook Handler
 * Handles Library service actions
 */

// Only handle library service
if ($data['service'] === 'library') {
    $action = $data['action'];
    $playerId = $data['player_id'];
    
    // Get database instance
    if (!class_exists('Database')) {
        require_once __DIR__ . '/../../../../includes/database.php';
    }
    $db = Database::getInstance();
    
    // Get player data
    $player = $db->fetchOne("SELECT * FROM players WHERE id = ?", [$playerId]);
    if (!$player) {
        $data['handled'] = true;
        $data['result'] = ['success' => false, 'message' => 'Player not found'];
        return $data;
    }
    
    switch ($action) {
        case 'research':
        case 'study':
            $expGain = 25;
            $goldCost = 10;
            
            // Validate player has enough gold
            if ($player['gold'] < $goldCost) {
                $data['handled'] = true;
                $data['result'] = [
                    'success' => false,
                    'message' => "You need {$goldCost} gold to study at the library."
                ];
                return $data;
            }
            
            // Update player stats
            $db->query(
                "UPDATE players SET gold = gold - ?, experience = experience + ? WHERE id = ?",
                [$goldCost, $expGain, $playerId]
            );
            
            // Get updated player data
            $updatedPlayer = $db->fetchOne("SELECT * FROM players WHERE id = ?", [$playerId]);
            
            $data['handled'] = true;
            $data['result'] = [
                'success' => true,
                'message' => "You spend time studying ancient tomes. You gain {$expGain} experience!",
                'exp_gain' => $expGain,
                'gold_spent' => $goldCost,
                'player' => [
                    'gold' => $updatedPlayer['gold'],
                    'experience' => $updatedPlayer['experience']
                ]
            ];
            break;
            
        case 'view':
        case 'info':
            $data['handled'] = true;
            $data['result'] = [
                'success' => true,
                'service' => 'library',
                'name' => 'Library',
                'description' => 'Study ancient tomes and research knowledge to gain experience.',
                'modal_config' => [
                    'header_color' => '#8B4513',
                    'max_width' => '700px'
                ],
                'actions' => [
                    'research' => [
                        'name' => 'Research',
                        'description' => 'Spend 10 gold to gain 25 experience',
                        'cost' => 10
                    ],
                    'study' => [
                        'name' => 'Study',
                        'description' => 'Spend 10 gold to gain 25 experience',
                        'cost' => 10
                    ]
                ]
            ];
            break;
            
        default:
            $data['handled'] = true;
            $data['result'] = ['success' => false, 'message' => 'Unknown library action'];
    }
}

return $data;
?>

Creating Your First Plugin

  1. Create a folder with your plugin ID (e.g., my-first-plugin)
  2. Create plugin.json with required fields
  3. Create hooks/ directory and add hook handlers
  4. Add assets/ if needed
  5. Test locally by uploading via admin portal
  6. Create ZIP file with proper structure
  7. Upload to marketplace

Advanced Topics

Accessing Game Systems

In your hook files, you can access core game systems:

// Database access
if (!class_exists('Database')) {
    require_once __DIR__ . '/../../../../includes/database.php';
}
$db = Database::getInstance();

// Get player data
$player = $db->fetchOne("SELECT * FROM players WHERE id = ?", [$playerId]);

// Update player
$db->query("UPDATE players SET gold = gold - ? WHERE id = ?", [$amount, $playerId]);

// Plugin configuration
$pluginManager = PluginManager::getInstance();
$config = $pluginManager->getPluginConfig('your-plugin-id');
$apiKey = $config['api_key'] ?? '';

Error Handling

Always handle errors gracefully in your hooks:

try {
    // Your plugin logic
    $result = performAction();
    
    $data['handled'] = true;
    $data['result'] = [
        'success' => true,
        'message' => 'Action completed successfully'
    ];
} catch (Exception $e) {
    error_log("Plugin error: " . $e->getMessage());
    
    $data['handled'] = true;
    $data['result'] = [
        'success' => false,
        'message' => 'An error occurred. Please try again.'
    ];
}

return $data;

Best Practices

Common Patterns

Pattern 1: Conditional Service Addition

// Only add service to specific location
if ($data['location_key'] === 'town') {
    $data['services']['my_service'] = [
        'name' => 'My Service',
        'description' => 'Service description',
        'icon' => 'custom-icon'
    ];
}
return $data;

Pattern 2: Configuration-Based Behavior

// Get plugin configuration
$pluginManager = PluginManager::getInstance();
$config = $pluginManager->getPluginConfig('your-plugin-id');

$enabled = $config['enabled'] ?? true;
$maxItems = $config['max_items'] ?? 10;

if ($enabled) {
    // Plugin logic using $maxItems
}

Pattern 3: Player State Validation

// Check player state before action
$player = $db->fetchOne("SELECT * FROM players WHERE id = ?", [$playerId]);

if ($player['gold'] < $requiredGold) {
    $data['handled'] = true;
    $data['result'] = [
        'success' => false,
        'message' => "You need {$requiredGold} gold for this action."
    ];
    return $data;
}

// Proceed with action...

Testing Your Plugin

  1. Local Testing: Upload via admin portal and test all functionality
  2. Error Testing: Test error cases (insufficient gold, missing items, etc.)
  3. Edge Cases: Test with boundary values and unusual inputs
  4. Multiple Players: Test with different player accounts
  5. Plugin Conflicts: Test with other plugins enabled
  6. Version Compatibility: Test with different game versions if applicable
✅ Testing Checklist:
  • All actions work correctly
  • Error messages are clear and helpful
  • Player stats update correctly
  • UI displays properly (modals, icons, etc.)
  • No PHP errors or warnings
  • No security vulnerabilities

Publishing Your Plugin

Preparing Your Plugin

  1. Test thoroughly - Test all functionality locally
  2. Check version compatibility - Ensure your plugin works with current game version
  3. Document your plugin - Include a README.md
  4. Create ZIP file - Include all files in correct structure

ZIP File Structure

Your ZIP should contain either:

# Correct structure (Option 1)
my-plugin.zip
  ├── plugin.json
  ├── hooks/
  │   └── service_action.php
  └── assets/
      ├── icon.png
      ├── header.jpg
      └── action-icons/
          └── research.png

# Also correct (Option 2)
my-plugin.zip
  └── my-plugin/
      ├── plugin.json
      ├── hooks/
      │   └── service_action.php
      └── assets/
          ├── icon.png
          └── header.jpg
✅ Asset Handling: The plugin manager automatically preserves the entire folder structure when installing plugins. All files in your assets/ directory will be copied to plugins/{plugin-id}/assets/ during installation. Make sure to include all assets in your ZIP file!

Uploading to Marketplace

  1. Log in to the marketplace
  2. Go to your Dashboard
  3. Click "Upload Plugin"
  4. Select your ZIP file
  5. Wait for validation
  6. Plugin will be reviewed and approved
💡 Note: If you're updating an existing plugin, ensure the version number is incremented and the publisher matches the original.

Additional Resources

Ready to Create?

Start building amazing plugins for DragonLord!

Create Account