Learn how to create powerful plugins for DragonLord
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.
plugin.json manifestEvery plugin must follow a specific folder structure. Here's the standard layout:
The manifest file defines your plugin's metadata, requirements, hooks, and permissions.
| 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) |
| 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) |
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
}
The requires object allows your plugin to depend on other plugins:
"requires": {
"dependencies": [
"another-plugin-id",
"yet-another-plugin"
]
}
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"
}
}
| 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 |
PluginManager::getInstance()->getPluginConfig($pluginId) to retrieve saved configuration values.
{
"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"
}
}
}
Adds new services to existing game locations.
{
"type": "location_extension",
"target_location": "town"
}
Adds new quests to the game.
{
"type": "quest_pack"
}
Provides new icons, images, or other assets.
{
"type": "asset_pack"
}
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"
}
| 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 |
<?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;
?>
$data array. For service actions, set $data['handled'] = true and $data['result'] to indicate the action was processed.
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.'
];
| 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 |
Plugins must declare permissions for security. The sandbox system enforces these restrictions.
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"
]
}
hooks objectControls file system access for reading and writing files.
"permissions": {
"file_system": {
"read": [
"/plugins/{plugin_id}/assets",
"/public/icons"
],
"write": [
"/plugins/{plugin_id}/cache"
]
}
}
{plugin_id} placeholder for plugin-specific pathsControls database table access for reading and writing data.
"permissions": {
"database": {
"read": ["players", "items"],
"write": ["custom_plugin_table"]
}
}
"permissions": {
"execute_hooks": [
"location_services_init",
"location_service_action"
],
"file_system": {
"read": ["/plugins/{plugin_id}/assets"],
"write": []
},
"database": {
"read": ["players"],
"write": []
}
}
Plugins can include custom icons, images, and other assets. Place them in the assets/ directory.
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 paths are resolved in the following priority order:
http:// or https://, used as-is
'icon' => 'https://example.com/my-icon.png'
/, relative to game base path
'icon' => '/public/icons/custom.png'
plugins/{plugin-id}/assets/{path}
'icon' => 'assets/library-icon.png'
// Resolves to: plugins/location-extension-library/assets/library-icon.png
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>'
]
]
];
| 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 |
| 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) |
See the example plugin: plugins/location-extension-library/
This plugin adds a Library service to Town with research actions.
{
"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"
}
}
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;
?>
my-first-plugin)plugin.json with required fieldshooks/ directory and add hook handlersassets/ if neededIn 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'] ?? '';
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;
error_log() for debugging (remove in production)// 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;
// 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
}
// 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...
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
assets/ directory will be copied to plugins/{plugin-id}/assets/ during installation. Make sure to include all assets in your ZIP file!
plugins/ directory of your game installation