Skip to content

RedCubeOfficial/PocketMine-MP_Plugin_Guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

PocketMine-MP Plugin Development Tutorial

Table of Contents

  1. Introduction to PocketMine-MPz
  2. Setting Up Your Development Environment
  3. Creating Your First Plugin
  4. Plugin Structure
  5. Plugin.yml File
  6. Main Plugin Class
  7. Events and Event Handling
  8. Commands
  9. Permissions
  10. Config Files
  11. Forms API
  12. Database Integration
  13. Best Practices
  14. Publishing Your Plugin
  15. Advanced Topics

Introduction to PocketMine-MP

PocketMine-MP (PMMP) is a server software for Minecraft: Bedrock Edition written in PHP. It allows developers to create custom plugins to extend the functionality of Minecraft servers.

What is a Plugin?

A plugin is a package of code that adds new features or modifies existing ones in the PMMP server. Plugins can:

  • Add new commands
  • Create custom items and blocks
  • Modify game mechanics
  • Add mini-games
  • Implement economy systems
  • And much more!

Setting Up Your Development Environment

Requirements

  • PHP 8.0 or newer
  • Composer (PHP package manager)
  • A code editor (VS Code, PHPStorm, etc.)
  • PocketMine-MP server for testing

Installation Steps

  1. Install PHP:

    • Windows: Download from windows.php.net
    • Linux: sudo apt install php8.0-cli php8.0-xml php8.0-mbstring php8.0-gd php8.0-curl
    • macOS: brew install php
  2. Install Composer:

  3. Download PocketMine-MP:

    • Get the latest release from pmmp.io
    • Extract the files to a directory
  4. Setup IDE:

    • Install a PHP-compatible IDE like Visual Studio Code or PHPStorm
    • Install PHP extensions/plugins for your IDE

Creating Your First Plugin

Let's create a simple "Hello World" plugin that welcomes players when they join.

1. Create Plugin Directory Structure

MyFirstPlugin/
├── src/
│   └── YourName/
│       └── MyFirstPlugin/
│           └── Main.php
└── plugin.yml

2. Create plugin.yml

The plugin.yml file contains metadata about your plugin:

name: MyFirstPlugin
main: YourName\MyFirstPlugin\Main
version: 1.0.0
api: 5.0.0
author: YourName
description: My first PocketMine-MP plugin

3. Create Main.php

<?php

declare(strict_types=1);

namespace YourName\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\plugin\PluginBase;
use pocketmine\utils\TextFormat;

class Main extends PluginBase implements Listener {

    protected function onEnable(): void {
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
        $this->getLogger()->info("MyFirstPlugin has been enabled!");
    }

    protected function onDisable(): void {
        $this->getLogger()->info("MyFirstPlugin has been disabled!");
    }

    public function onPlayerJoin(PlayerJoinEvent $event): void {
        $player = $event->getPlayer();
        $player->sendMessage(TextFormat::GREEN . "Welcome to the server, " . $player->getName() . "!");
    }
}

Plugin Structure

Directory Structure

A typical PMMP plugin follows this structure:

PluginName/
├── resources/         # Config files, assets, etc.
│   └── config.yml
├── src/
│   └── Author/
│       └── PluginName/
│           ├── Main.php
│           ├── commands/     # Command classes
│           ├── listeners/    # Event listener classes
│           └── utils/        # Utility classes
├── plugin.yml         # Plugin metadata
└── README.md          # Documentation

Plugin.yml File

The plugin.yml file is required for all plugins and contains essential metadata:

name: PluginName                       # Plugin name
main: Author\PluginName\Main           # Main class (namespace\class)
version: 1.0.0                         # Plugin version
api: 5.0.0                             # PMMP API version
author: YourName                       # Author name
description: Plugin description        # Short description
website: https://example.com           # Optional website
depend: [AnotherPlugin]                # Required plugins
softdepend: [OptionalPlugin]           # Optional plugins

# Commands (optional)
commands:
  examplecmd:
    description: Example command
    usage: "/examplecmd <argument>"
    permission: pluginname.command.example
    aliases: [excmd, ecmd]

# Permissions (optional)
permissions:
  pluginname.command.example:
    description: Allows using the example command
    default: op

Main Plugin Class

The main class of your plugin must extend PluginBase and is the entry point for your plugin:

<?php

declare(strict_types=1);

namespace Author\PluginName;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase {

    protected function onEnable(): void {
        // Called when the plugin is enabled
        $this->saveDefaultConfig(); // Saves default config.yml if it doesn't exist
        
        // Register commands
        $this->getServer()->getCommandMap()->register("pluginname", new ExampleCommand($this));
        
        // Register event listeners
        $this->getServer()->getPluginManager()->registerEvents(new EventListener($this), $this);
        
        $this->getLogger()->info("Plugin has been enabled!");
    }

    protected function onDisable(): void {
        // Called when the plugin is disabled
        $this->getLogger()->info("Plugin has been disabled!");
    }
}

Events and Event Handling

Events are triggered when something happens in the game. Your plugin can listen for these events and respond accordingly.

Creating an Event Listener

<?php

declare(strict_types=1);

namespace Author\PluginName\listeners;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\event\player\PlayerQuitEvent;
use pocketmine\event\block\BlockBreakEvent;
use Author\PluginName\Main;

class EventListener implements Listener {

    private Main $plugin;

    public function __construct(Main $plugin) {
        $this->plugin = $plugin;
    }

    /**
     * @param PlayerJoinEvent $event
     * @priority NORMAL
     */
    public function onPlayerJoin(PlayerJoinEvent $event): void {
        $player = $event->getPlayer();
        // Do something when a player joins
    }

    /**
     * @param PlayerQuitEvent $event
     */
    public function onPlayerQuit(PlayerQuitEvent $event): void {
        $player = $event->getPlayer();
        // Do something when a player leaves
    }

    /**
     * @param BlockBreakEvent $event
     * @priority HIGH
     * @ignoreCancelled true
     */
    public function onBlockBreak(BlockBreakEvent $event): void {
        // This will only be called if the event wasn't cancelled by another plugin
        // And it will run after NORMAL priority listeners but before MONITOR
        
        $player = $event->getPlayer();
        $block = $event->getBlock();
        
        // Cancel the event to prevent the block from breaking
        // $event->cancel();
    }
}

Common Events

  • PlayerJoinEvent - When a player joins the server
  • PlayerQuitEvent - When a player leaves the server
  • PlayerChatEvent - When a player sends a chat message
  • BlockBreakEvent - When a player breaks a block
  • BlockPlaceEvent - When a player places a block
  • EntityDamageEvent - When an entity takes damage
  • PlayerInteractEvent - When a player interacts with a block or air

Commands

Commands allow players to interact with your plugin through text input.

Creating a Command

<?php

declare(strict_types=1);

namespace Author\PluginName\commands;

use pocketmine\command\Command;
use pocketmine\command\CommandSender;
use pocketmine\player\Player;
use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginOwned;
use pocketmine\utils\TextFormat;
use Author\PluginName\Main;

class ExampleCommand extends Command implements PluginOwned {

    private Main $plugin;

    public function __construct(Main $plugin) {
        parent::__construct("example", "An example command", "/example <argument>", ["ex", "examplecmd"]);
        $this->setPermission("pluginname.command.example");
        $this->plugin = $plugin;
    }

    public function execute(CommandSender $sender, string $commandLabel, array $args): bool {
        if (!$this->testPermission($sender)) {
            return false;
        }

        if (!$sender instanceof Player) {
            $sender->sendMessage(TextFormat::RED . "This command can only be used in-game");
            return false;
        }

        if (count($args) < 1) {
            $sender->sendMessage(TextFormat::RED . "Usage: " . $this->getUsage());
            return false;
        }

        // Command logic here
        $sender->sendMessage(TextFormat::GREEN . "You used the example command with argument: " . $args[0]);
        return true;
    }

    public function getOwningPlugin(): Plugin {
        return $this->plugin;
    }
}

Permissions

Permissions control what players can and cannot do on your server.

Defining Permissions in plugin.yml

permissions:
  pluginname:
    default: false
    description: Root permission for all plugin features
    children:
      pluginname.command:
        default: false
        description: Permission for all commands
        children:
          pluginname.command.example:
            default: op
            description: Permission for the example command
      pluginname.feature:
        default: true
        description: Permission to use a specific feature

Permission Defaults

  • op: Only operators have this permission by default
  • true: Everyone has this permission by default
  • false: No one has this permission by default
  • not op: Everyone except operators have this permission by default

Checking Permissions

if ($player->hasPermission("pluginname.command.example")) {
    // Player has permission
} else {
    // Player doesn't have permission
    $player->sendMessage(TextFormat::RED . "You don't have permission to use this command!");
}

Config Files

Config files allow you to make your plugin customizable without changing the code.

Default Config

Create a file named config.yml in the resources directory:

# Example config.yml
settings:
  enable-feature: true
  cooldown: 10
messages:
  welcome: "Welcome to the server!"
  goodbye: "Goodbye!"

Loading and Using Config

protected function onEnable(): void {
    // Save default config if it doesn't exist
    $this->saveDefaultConfig();
    
    // Get values from config
    $enableFeature = $this->getConfig()->get("settings.enable-feature", true);
    $welcomeMessage = $this->getConfig()->get("messages.welcome", "Welcome!");
    
    // You can also reload the config
    $this->reloadConfig();
}

Custom Config Files

// Save a custom config file from resources
$this->saveResource("custom.yml");

// Load a custom config file
$customConfig = new Config($this->getDataFolder() . "custom.yml", Config::YAML);
$value = $customConfig->get("key", "default");

// Save changes to custom config
$customConfig->set("key", "new value");
$customConfig->save();

Forms API

Forms provide a graphical interface for players to interact with your plugin.

Installation

To use forms, you'll need the FormAPI library. You can include it in your plugin using Composer:

{
    "require": {
        "jojoe77777/formapi": "^2.1"
    }
}

Simple Form (Menu)

use jojoe77777\FormAPI\SimpleForm;

public function sendSimpleForm(Player $player): void {
    $form = new SimpleForm(function (Player $player, ?int $data) {
        if ($data === null) {
            // Form was closed without selecting an option
            return;
        }
        
        switch ($data) {
            case 0:
                $player->sendMessage("You selected Option 1");
                break;
            case 1:
                $player->sendMessage("You selected Option 2");
                break;
        }
    });
    
    $form->setTitle("Example Form");
    $form->setContent("Please select an option:");
    $form->addButton("Option 1", 0, "textures/ui/icon1");
    $form->addButton("Option 2", 0, "textures/ui/icon2");
    $form->sendToPlayer($player);
}

Custom Form (Input Fields)

use jojoe77777\FormAPI\CustomForm;

public function sendCustomForm(Player $player): void {
    $form = new CustomForm(function (Player $player, ?array $data) {
        if ($data === null) {
            // Form was closed
            return;
        }
        
        // $data[0] is the first input (name)
        // $data[1] is the dropdown selection
        // $data[2] is the slider value
        // $data[3] is the toggle state
        
        $player->sendMessage("Name: " . $data[0]);
        $player->sendMessage("Option: " . $data[1]);
        $player->sendMessage("Value: " . $data[2]);
        $player->sendMessage("Enabled: " . ($data[3] ? "Yes" : "No"));
    });
    
    $form->setTitle("Settings Form");
    $form->addInput("Name", "Enter your name", "Steve");
    $form->addDropdown("Select Option", ["Option A", "Option B", "Option C"]);
    $form->addSlider("Select Value", 1, 100, 5, 50);
    $form->addToggle("Enable Feature", true);
    $form->sendToPlayer($player);
}

Database Integration

PMMP plugins can use various database systems to store data.

SQLite Example

private \SQLite3 $db;

protected function onEnable(): void {
    // Create database directory if it doesn't exist
    @mkdir($this->getDataFolder() . "data/");
    
    // Connect to SQLite database
    $this->db = new \SQLite3($this->getDataFolder() . "data/database.db");
    
    // Create tables if they don't exist
    $this->db->exec("CREATE TABLE IF NOT EXISTS players (
        uuid TEXT PRIMARY KEY,
        name TEXT,
        coins INTEGER DEFAULT 0,
        last_seen INTEGER
    )");
}

public function savePlayer(Player $player, int $coins): void {
    $stmt = $this->db->prepare("INSERT OR REPLACE INTO players (uuid, name, coins, last_seen) VALUES (:uuid, :name, :coins, :time)");
    $stmt->bindValue(":uuid", $player->getUniqueId()->toString(), SQLITE3_TEXT);
    $stmt->bindValue(":name", $player->getName(), SQLITE3_TEXT);
    $stmt->bindValue(":coins", $coins, SQLITE3_INTEGER);
    $stmt->bindValue(":time", time(), SQLITE3_INTEGER);
    $stmt->execute();
}

public function getPlayerCoins(string $uuid): int {
    $stmt = $this->db->prepare("SELECT coins FROM players WHERE uuid = :uuid");
    $stmt->bindValue(":uuid", $uuid, SQLITE3_TEXT);
    $result = $stmt->execute();
    
    if ($row = $result->fetchArray(SQLITE3_ASSOC)) {
        return (int) $row["coins"];
    }
    
    return 0;
}

protected function onDisable(): void {
    // Close database connection
    if (isset($this->db)) {
        $this->db->close();
    }
}

MySQL Example

private ?\mysqli $db = null;

protected function onEnable(): void {
    // Load database config
    $config = $this->getConfig();
    $host = $config->get("mysql.host", "localhost");
    $user = $config->get("mysql.user", "root");
    $pass = $config->get("mysql.password", "");
    $dbname = $config->get("mysql.database", "minecraft");
    $port = $config->get("mysql.port", 3306);
    
    // Connect to MySQL database
    try {
        $this->db = new \mysqli($host, $user, $pass, $dbname, $port);
        if ($this->db->connect_error) {
            throw new \Exception("Connection failed: " . $this->db->connect_error);
        }
        
        // Create tables if they don't exist
        $this->db->query("CREATE TABLE IF NOT EXISTS players (
            uuid VARCHAR(36) PRIMARY KEY,
            name VARCHAR(16) NOT NULL,
            coins INT DEFAULT 0,
            last_seen INT
        )");
        
        $this->getLogger()->info("Database connection established");
    } catch (\Exception $e) {
        $this->getLogger()->error("Database connection failed: " . $e->getMessage());
        $this->getServer()->getPluginManager()->disablePlugin($this);
    }
}

protected function onDisable(): void {
    // Close database connection
    if ($this->db !== null) {
        $this->db->close();
    }
}

Best Practices

Code Organization

  1. Separate concerns: Use different classes for different functionalities

    • Commands in a commands namespace
    • Event listeners in a listeners namespace
    • Data models in a models namespace
  2. Use namespaces properly: Follow PSR-4 autoloading standards

  3. Document your code: Use PHPDoc comments to document methods and classes

Performance

  1. Avoid heavy operations in event handlers: Events can be called frequently

  2. Use async tasks for database operations:

    $this->getServer()->getAsyncPool()->submitTask(new DatabaseTask($data));
  3. Cache frequently accessed data: Avoid repeated database queries

  4. Use scheduled tasks for periodic operations:

    $this->getScheduler()->scheduleRepeatingTask(new MyTask($this), 20 * 60); // Run every minute (20 ticks * 60)

Plugin Interaction

  1. Check if a plugin exists before using it:

    if ($this->getServer()->getPluginManager()->getPlugin("EconomyAPI") !== null) {
        // EconomyAPI is loaded
    }
  2. Use proper dependency management in plugin.yml:

    depend: [EconomyAPI]  # Plugin won't load without EconomyAPI
    softdepend: [WorldGuard]  # Plugin will load after WorldGuard if available

Publishing Your Plugin

  1. Create documentation:

    • README.md with installation and usage instructions
    • Wiki for detailed documentation (optional)
  2. License your plugin:

    • Choose an appropriate license (MIT, GPL, etc.)
    • Include a LICENSE file
  3. Create a release:

    • Package your plugin as a .phar file
    • Use tools like DevTools or PharBuilder
  4. Share your plugin:

Advanced Topics

Virions (Libraries)

Virions are reusable libraries that can be included in your plugins.

  1. Using virions:

    • Include the virion in your plugin using a tool like Poggit
    • Use the classes from the virion in your code
  2. Creating virions:

    • Create a separate project with reusable code
    • Follow the virion structure guidelines

Custom Items and Blocks

use pocketmine\item\ItemFactory;
use pocketmine\item\ItemIdentifier;
use pocketmine\item\ItemIds;

// Register a custom item
$factory = ItemFactory::getInstance();
$factory->register(new CustomItem(new ItemIdentifier(ItemIds::DIAMOND_SWORD, 100), "Custom Sword"), true);

Scheduled Tasks

use pocketmine\scheduler\Task;

class MyTask extends Task {
    private Main $plugin;
    
    public function __construct(Main $plugin) {
        $this->plugin = $plugin;
    }
    
    public function onRun(): void {
        // This code runs when the task executes
        $this->plugin->getLogger()->info("Task executed!");
    }
}

// Schedule a task to run once after 20 ticks (1 second)
$this->getScheduler()->scheduleDelayedTask(new MyTask($this), 20);

// Schedule a task to run every 20 ticks (1 second)
$this->getScheduler()->scheduleRepeatingTask(new MyTask($this), 20);

Async Tasks

use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;

class MyAsyncTask extends AsyncTask {
    private string $data;
    
    public function __construct(string $data) {
        $this->data = $data;
    }
    
    public function onRun(): void {
        // This runs in a separate thread
        $result = $this->performHeavyOperation($this->data);
        $this->setResult($result);
    }
    
    public function onCompletion(): void {
        // This runs in the main thread after the task completes
        $result = $this->getResult();
        Server::getInstance()->getLogger()->info("Async task completed with result: " . $result);
    }
    
    private function performHeavyOperation(string $data): string {
        // Simulate a heavy operation
        sleep(5);
        return "Processed: " . $data;
    }
}

// Submit an async task
$this->getServer()->getAsyncPool()->submitTask(new MyAsyncTask("test data"));

Conclusion

This tutorial has covered the basics of PocketMine-MP plugin development. As you become more comfortable with these concepts, you can create increasingly complex and feature-rich plugins.

Remember to:

  • Test your plugins thoroughly
  • Keep up with PMMP API changes
  • Join the PMMP community for support and collaboration
  • Share your plugins with others

Happy coding!

Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published