Skip to content

Latest commit

Β 

History

History
273 lines (219 loc) Β· 10.5 KB

File metadata and controls

273 lines (219 loc) Β· 10.5 KB

Dragonfly Plugin System

A powerful, language-agnostic plugin system for Dragonfly Minecraft Bedrock servers using gRPC and Protocol Buffers.

Why Dragonfly's Plugin System?

Benefit Description Use Case
🌍 Any Language Write plugins in JavaScript, TypeScript, PHP, Python, Rust, C++, or any language with gRPC support Use the language your team knows best
πŸ’° Sell Plugins Compile to binary (Rust, Go, C++) and distribute without source code Create commercial plugins
πŸ”₯ Hot Reload Edit JS/TS/PHP plugins and see changes instantly - no server restart needed Develop and debug plugins in real-time
πŸ“± Remote Control Plugins connect over gRPC - run them anywhere (phone app, cloud service, discord bot) Build mobile admin apps
πŸ“¦ Use Any Library Import npm packages on a Go server, use Python ML libraries, etc. Leverage entire ecosystems
⚑ Zero Performance Impact Plugins run in separate processes - slow/heavy plugin code doesn't affect server TPS Run intensive tasks without lag
πŸš€ High Performance (COMING SOON) Optimized protobuf protocol with optional batching for low latency Handle 100+ players with movement events
πŸ”’ Sandboxing Control what plugins can access via gRPC permissions Host untrusted plugins safely

Real-World Examples

# Hot reload: Edit plugin code while server is running
vim plugins/my-plugin.js   # Make changes
# Changes apply immediately - no restart!

# Remote plugin: Control server from your phone
# Plugin runs on your phone, connects to server over internet
phone-app β†’ [gRPC] β†’ Dragonfly Server

# Binary plugin: Sell without source code
rustc plugin.rs --release   # Compile to binary
# Distribute the binary - customers can't see your code

Features

  • Multi-Language Support: Write plugins in JavaScript, TypeScript, PHP, Python, Rust, C++, or any language with gRPC support
  • Event-Driven Architecture: Subscribe to specific events (player join, chat, block break, etc.)
  • Type Safety: Generated types for TypeScript and other statically typed languages

Quick Start

1. Clone the Repository

git clone https://github.com/secmc/plugin.git
cd plugin

2. Install Dependencies

go mod download

3. Configure Plugins

Edit plugins/plugins.yaml:

plugins:
  - id: my-plugin
    name: My First Plugin
    command: "node"
    args: ["examples/plugins/node/hello.js"]
    address: "127.0.0.1:50051"

4. Run the Server

go run main.go

Your plugin will automatically connect and start receiving events!

Example Plugins

We provide complete working examples in multiple languages:

  • TypeScript - Type-safe plugin with generated types (recommended for production)
  • Node.js - Simple JavaScript plugin
  • PHP - PHP plugin using gRPC extension

See examples/plugins/README.md for detailed documentation and more examples.

Creating Your First Plugin

Minimal Example (PHP)

<?php
// Example plugin showing command handling and block break event modification
require_once __DIR__ . '/vendor/autoload.php';

use Grpc\ChannelCredentials;

$pluginId = 'my-plugin';
$address = '127.0.0.1:50051';

$client = new \Df\Plugin\PluginClient($address, [
    'credentials' => ChannelCredentials::createInsecure(),
]);

$stream = $client->EventStream();

try {
    foreach ($stream->responses() as $message) {
        // Handle handshake
        if ($message->hasHello()) {
            $hello = new \DF\Plugin\PluginToHost();
            $hello->setPluginId($pluginId);
            $pluginHello = new \DF\Plugin\PluginHello();
            $pluginHello->setName('My Plugin');
            $pluginHello->setVersion('1.0.0');
            $pluginHello->setApiVersion($message->getHello()->getApiVersion());
            
            // Register /mine command
            $command = new \DF\Plugin\CommandSpec();
            $command->setName('/mine');
            $command->setDescription('Get mining boost');
            $pluginHello->setCommands([$command]);
            $hello->setHello($pluginHello);
            $stream->write($hello);

            // Subscribe to events
            $sub = new \DF\Plugin\PluginToHost();
            $sub->setPluginId($pluginId);
            $subscribe = new \DF\Plugin\EventSubscribe();
            $subscribe->setEvents([
                \DF\Plugin\EventType::PLAYER_JOIN,
                \DF\Plugin\EventType::COMMAND,
                \DF\Plugin\EventType::PLAYER_BLOCK_BREAK,
            ]);
            $sub->setSubscribe($subscribe);
            $stream->write($sub);
            continue;
        }

        if ($message->hasEvent()) {
            $event = $message->getEvent();
            
            // Handle /mine command
            if ($event->getType() === \DF\Plugin\EventType::COMMAND && $event->hasCommand()) {
                $cmd = $event->getCommand();
                if ($cmd->getCommand() === 'mine') {
                    // Send message to player
                    $action = new \DF\Plugin\Action();
                    $send = new \DF\Plugin\SendChatAction();
                    $send->setTargetUuid($cmd->getPlayerUuid());
                    $send->setMessage('Β§6⛏️ Mining boost activated! Break blocks for double drops!');
                    $action->setSendChat($send);
                    $batch = new \DF\Plugin\ActionBatch();
                    $batch->setActions([$action]);
                    $resp = new \DF\Plugin\PluginToHost();
                    $resp->setPluginId($pluginId);
                    $resp->setActions($batch);
                    $stream->write($resp);
                }
                
                // Acknowledge event
                $result = new \DF\Plugin\EventResult();
                $result->setEventId($event->getEventId());
                $result->setCancel(false);
                $resp = new \DF\Plugin\PluginToHost();
                $resp->setPluginId($pluginId);
                $resp->setEventResult($result);
                $stream->write($resp);
            }
            
            // Handle block break with double drops
            if ($event->getType() === 'BLOCK_BREAK' && $event->hasBlockBreak()) {
                $blockBreak = $event->getBlockBreak();
                echo "[php] {$blockBreak->getName()} broke block at ";
                echo "{$blockBreak->getX()},{$blockBreak->getY()},{$blockBreak->getZ()}\n";
                
                // Give double drops for every 10th block (X coordinate % 10 == 0)
                if ($blockBreak->getX() % 10 === 0) {
                    $drop = new \DF\Plugin\ItemStack();
                    $drop->setName('minecraft:diamond');
                    $drop->setCount(2);
                    $drop->setMeta(0);
                    
                    $mutation = new \DF\Plugin\BlockBreakMutation();
                    $mutation->setDrops([$drop]);
                    $mutation->setXp(10);
                    
                    $result = new \DF\Plugin\EventResult();
                    $result->setEventId($event->getEventId());
                    $result->setBlockBreak($mutation);
                    $resp = new \DF\Plugin\PluginToHost();
                    $resp->setPluginId($pluginId);
                    $resp->setEventResult($result);
                    $stream->write($resp);
                } else {
                    // Acknowledge normally
                    $result = new \DF\Plugin\EventResult();
                    $result->setEventId($event->getEventId());
                    $result->setCancel(false);
                    $resp = new \DF\Plugin\PluginToHost();
                    $resp->setPluginId($pluginId);
                    $resp->setEventResult($result);
                    $stream->write($resp);
                }
            }
        }
    }
} catch (Exception $e) {
    echo "[php] Error: " . $e->getMessage() . "\n";
} finally {
    $stream->writesDone();
}

echo "[php] plugin connected to {$address}\n";

Project Structure

dragonfly-plugins/
β”œβ”€β”€ dragonfly/              # Modified Dragonfly server with plugin support
β”œβ”€β”€ plugin/                 # Plugin system core
β”‚   β”œβ”€β”€ proto/             # Protocol Buffer definitions
β”‚   β”œβ”€β”€ manager.go         # Plugin lifecycle management
β”‚   └── README.md          # Plugin system documentation
β”œβ”€β”€ examples/
β”‚   └── plugins/           # Example plugins in various languages
β”œβ”€β”€ plugins/
β”‚   └── plugins.yaml       # Plugin configuration
└── main.go                # Server entry point

How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         gRPC Stream          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 β”‚ ←──────────────────────────→  β”‚                  β”‚
β”‚  Dragonfly      β”‚   Events: JOIN, CHAT, etc.   β”‚  Your Plugin     β”‚
β”‚  Server         β”‚   Actions: TELEPORT, etc.    β”‚  (Any Language)  β”‚
β”‚  (Go)           β”‚                               β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. Server starts and loads plugin configuration from plugins/plugins.yaml
  2. Plugin process launches via configured command (e.g., node plugin.js)
  3. Handshake occurs where plugin registers capabilities
  4. Plugin subscribes to events it wants to receive
  5. Events flow from server to plugin in real-time
  6. Plugin executes actions by sending messages back to server

Documentation

Protobuf generation

to generate our protobuf types, you will need to install buf and protoc-gen-go:

# Install buf
# Follow instructions at https://buf.build/docs/cli/installation/

# Install protoc-gen-go and protoc-gen-go-grpc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

Then run:

make proto