Developer guide · API 1.6

Develop server plugins

Plugins are .NET 9 class libraries loaded in-process by the authoritative server. The API uses familiar lifecycle, event, command, and scheduler patterns with idiomatic C#.

Quick start

Install the .NET 9 SDK, copy examples/PluginStarter, and rename the project, source file, namespace, class, and manifest values. Your project should reference the API without copying it into the output:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\BlockGame.PluginApi\BlockGame.PluginApi.csproj"
                      Private="false" />
    <None Include="plugin.yml" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
</Project>

Declare plugin.yml

name: MyPlugin
version: 1.0.0
main: MyPlugin.dll
entrypoint: MyPlugin.MyPlugin
api-version: 1.6
# depend: [RequiredPlugin]
# softdepend: [OptionalPlugin]
KeyMeaning
nameUnique display name and data-directory name.
versionYour plugin version, shown in server logs.
mainMain assembly path relative to the manifest.
entrypointFully qualified public IGamePlugin type.
api-versionOldest API contract the plugin requires.
dependPlugins that must be installed and enabled first.
softdependOptional integrations that influence load order when present.

API versions use major-version compatibility: an API 1.6 server accepts plugins targeting 1.0 through 1.6, but not 2.0.

Implement the lifecycle

using BlockGame.PluginApi;

namespace MyPlugin;

public sealed class MyPlugin : GamePlugin
{
    public override string Name => "MyPlugin";
    public override string Version => "1.0.0";

    public override void OnLoad()
    {
        // Read configuration and initialize plugin-owned state.
    }

    public override void OnEnable()
    {
        Server.Log("Enabled");
    }

    public override void OnDisable()
    {
        // Flush plugin-owned state.
    }
}

Server and DataDirectory are available from OnLoad onward. Required dependencies enable first. Plugins disable in reverse enable order; listeners and scheduled tasks are removed automatically.

Register commands

Server.RegisterCommand(new PluginCommand(
    "greet",
    "Greets the command sender",
    command => command.Reply($"Hello, {command.Player.Name}!"),
    usage: "/greet",
    permission: "myplugin.greet",
    aliases: new[] { "hi" }));

Grant custom permission nodes in permissions.cfg. A command receives a stable player snapshot, parsed arguments, the raw command, and a reply callback.

Listen to typed events

Server.RegisterListener<BlockEditEventArgs>(e =>
{
    if (e.Position.Y < 20)
    {
        e.Cancelled = true;
        e.DenialMessage = "You cannot build below the arena.";
    }
}, EventPriority.High);

Priorities run from Lowest through Monitor. Use Monitor only to observe. Set ignoreCancelled: true to skip an event already cancelled by an earlier listener.

Schedule server-thread work

Server.RunTask(() => Server.Broadcast("Next tick"));
Server.RunTaskLater(TimeSpan.FromSeconds(5), StartRound);
Server.RunTaskTimer(TimeSpan.Zero, TimeSpan.FromSeconds(1), UpdateCountdown);

World and player callbacks run on the server tick thread. Keep them short; never block that thread on file, database, or network I/O.

Store plugin-owned data

Write configuration and persistent metadata beneath DataDirectory, not beside the installed DLL. Use stable PluginPlayer.Id values as player keys. Treat PluginPlayer objects as snapshots rather than live mutable state.

API 1.6 reference

AreaAvailable capabilities
LifecycleOnLoad, OnEnable, OnDisable
EventsJoin, quit, chat, tick, block edit, attack, damage, and death events
PlayersLookup, message, teleport, game mode, disguise, health, inventory, armor, and permissions
WorldBlock reads/writes, selections, bounded region capture and restore
ContainersRead, set, and clear item counts in authoritative chests
PersistenceHome chunks, claim enumeration, claim/release, chunk generation, and world saves
UtilitiesCommands, broadcasts, prefixed logging, delayed and repeating tasks

Use stable IDs from PluginBlocks and PluginItems. The API deliberately does not expose networking, rendering, server connection classes, or internal world types.

Minigame SDK

Round-based modes can reference BlockGame.PluginApi.Minigames and derive from MinigamePlugin. It activates only on a matching minigame backend and supplies player and announcement helpers. Ship BlockGame.PluginApi.Minigames.dll with plugins that use it.

Build, package, and test

powershell -NoProfile -ExecutionPolicy Bypass `
  -File .\tools\package-plugin.ps1 `
  .\examples\MyPlugin\MyPlugin.csproj

The packager validates the manifest and assembly, excludes debug symbols and the server-supplied API, and creates both a directory and ZIP beneath artifacts/plugins.

  1. Test against the oldest API version declared in your manifest.
  2. Install the packaged directory beneath Plugins.
  3. Restart the server and confirm the enabled message in its logs.
  4. Exercise disable and restart paths, missing configuration, and dependency failures before publishing.

Distribution rule: include your main DLL, plugin.yml, and private third-party dependencies. Do not ship BlockGame.PluginApi.dll.