Files
LCE-Revelations/Minecraft.Server.FourKit/docs/plugin-creation.md
itsRevela 42a582fb9f feat: add FourKit plugin host with dual server build
Adds the FourKit .NET 10 plugin host as a second dedicated server
build flavour alongside the existing vanilla server. Both flavours
build from the same source tree, with FourKit gated by the
MINECRAFT_SERVER_FOURKIT_BUILD preprocessor define.

Build layout:

  Minecraft.Server         vanilla, no plugin support, no .NET dep
  Minecraft.Server.FourKit FourKit-enabled, ships with bundled
                           .NET 10 self-contained runtime in runtime/
                           and an empty plugins/ folder

Both produce a Minecraft.Server.exe in their own per-target output
dir. The variant identity lives in the directory name, not the
binary name, so either flavour can be shipped as a drop-in.

Native bridge (Minecraft.Server/FourKit*.{cpp,h}):

* FourKitRuntime: hosts CoreCLR via hostfxr's command-line init API
  (the runtime-config API does not support self-contained components)
* FourKitBridge: ~50 Fire* event entry points, with inline no-op
  stubs for the standalone build so gameplay code can call them
  unconditionally
* FourKitNatives: ~80 native callbacks the managed side invokes
  for player/world/inventory mutations
* FourKitMappers: type and enum mapping helpers

Managed plugin host (Minecraft.Server.FourKit/):

* Bukkit-style API: Player, World, Block, Inventory, Command,
  Listener, EventHandler attribute, ~54 event classes
* PluginLoader with per-plugin AssemblyLoadContext
* FourKitHost as the [UnmanagedCallersOnly] entry point table
* Runtime resolves plugins relative to the host process so they
  always live next to Minecraft.Server.exe regardless of where the
  managed assembly itself is loaded from

Engine hooks (Minecraft.Client/, Minecraft.World/):

* Player lifecycle (PreLogin, Login, Join, Quit, Kick, Move,
  Teleport, Portal, Death) wired into PendingConnection and
  PlayerConnection without disturbing the cipher handshake or
  identity-token security flow
* Inventory open/click/drop hooks across every container menu type
* Block place/break/grow/burn/spread/from-to hooks across the
  full tile family
* Bed enter/leave, sign change, entity damage/death, ender pearl
  teleport hooks

Regression fixes preserved while applying donor diffs:

* ServerPlayer::die() retains the LCE-Revelations hardcore branch
  (setGameMode(ADVENTURE) + banPlayerForHardcoreDeath) in both the
  FourKit and non-FourKit code paths
* ServerLevel::entityAdded() retains the sub-entity ID reassignment
  loop required by the client's handleAddMob offset, fixing Ender
  Dragon and Wither boss multi-part hit detection
* LivingEntity::travel() retains the raw Player* cast and the
  cached frictionTile, both Revelations perf wins that the donor
  silently reverted
* ServerLogger.cpp keeps the file-logging code donor stripped
* PlayerList.cpp end portal transition fix and UIScene_EndPoem
  bounds-check are intact

Build system:

* Top-level CMakeLists.txt adds the Minecraft.Server.FourKit
  subdirectory and pulls in the new shared cmake/ServerTarget.cmake
  helper
* Minecraft.Server/cmake/sources/Common.cmake is now location
  independent (uses CMAKE_CURRENT_LIST_DIR) so the source list
  can be consumed from either server target's CMakeLists.txt
* The seven FourKit*.cpp/h files live in their own
  _MINECRAFT_SERVER_COMMON_SERVER_FOURKIT variable so the
  standalone target omits them
* configure-time .NET 10 SDK check fails fast with a clear
  download link if the SDK is missing
* global.json pins the SDK to 10.0.100 with latestFeature
  rollforward

Sample plugin (samples/HelloPlugin/) demonstrates the loader and
the PlayerJoinEvent listener pattern.

CI:

* nightly.yml builds both server flavours, ships
  LCE-Revelations-Server-Win64.zip and
  LCE-Revelations-Server-Win64-FourKit.zip, attests both, and
  updates release notes for the dual-flavour layout
* pull-request.yml pulls in actions/setup-dotnet so the FourKit
  publish step works in PR validation
* All zip artifacts and the client zip are renamed from
  LCREWindows64 to LCE-Revelations-{Client,Server}-Win64

Documentation:

* COMPILE.md gets a VS 2022 quick start, .NET 10 prereq section,
  server flavours explanation, and a troubleshooting section
* docs/FOURKIT_PORT_RECON.md captures the file-by-file recon that
  drove the port
* docs/FOURKIT_PARITY.md is the canonical reference for which
  events FourKit fires

Docker:

* docker-compose.dedicated-server.yml MC_RUNTIME_DIR default points
  at the vanilla CMake output. The FourKit Docker image is
  intentionally NOT shipped yet because hosting .NET 10 self
  contained inside Wine has not been smoke-tested
2026-04-08 03:02:48 -05:00

303 lines
9.4 KiB
Markdown

@page plugin-creation Creating your first Plugin
@section main-plugin Initialization
This will go over how to create your first plugin.
If you havent already, be sure to set up your development environment first:
@ref setup
Plugins must have a class that extends \ref Minecraft.Server.FourKit.Plugin.ServerPlugin "ServerPlugin".
```csharp
using Minecraft.Server.FourKit;
using Minecraft.Server.FourKit.Plugin;
public class CoolPlugin : ServerPlugin
{
public override string name => "My Cool Plugin";
public override string version => "1.0.0";
public override string author => "Me";
public override void onEnable() { }
public override void onDisable() { }
}
```
`onEnable()` is ran when the server starts. This is where you add listeners and anything else you need to do on startup.
`onDisable()` runs when the server stops. You can do stuff like cleaning up here.
@section listeners Listeners
Listeners are vital for events to be intercepted by your plugin. This will go over the usage and how to get started.
Listeners must implement the \ref Minecraft.Server.FourKit.Event.Listener "Listener" interface. Your listener class should look like this:
```csharp
using Minecraft.Server.FourKit.Event;
public class MyListener : Listener
{
}
```
### Registering your listener
To register a listener, you need to add it to FourKit, a common place to do this is in `onEnable()` in your plugin.
```csharp
public override void onEnable() {
FourKit.addListener(new MyListener());
}
```
### Listening to Events
Now that we've registered the listener, we need to make it actually listen to events!
To listen to any given event in your listener class, you MUST create a method with the \ref Minecraft.Server.FourKit.Event.EventHandlerAttribute "EventHandler" attribute attached and the event specified by the type in the methods argument. The method can be named whatever you wish. Example:
```csharp
using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Event.Player;
public class MyListener : Listener
{
[EventHandler]
public void onPlayerJoin(PlayerJoinEvent e) {
}
}
```
This method will fire whenever a player joins the server. We can make it broadcast a greeting to the whole server:
```csharp
using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Event.Player;
public class MyListener : Listener
{
[EventHandler]
public void onPlayerJoin(PlayerJoinEvent e) {
FourKit.broadcastMessage("Welcome!");
}
}
```
### Manipulating Events
You may modify what happens with most events and also obtain information about the given event. These functions are stored in the Event object in your method. Let's modify the message that is broadcasted when a player joins the server:
```csharp
using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Event.Player;
public class MyListener : Listener
{
[EventHandler]
public void onPlayerJoin(PlayerJoinEvent e) {
event.setJoinMessage("Welcome, " + event.getPlayer().getName() + "!");
}
}
```
### What can I listen to?
You can browse through the \ref Minecraft.Server.FourKit.Event "Event" namespace to see all events that you can use.
@ref usage-of-all-events
@section advanced-functions Advanced Functions
### EventHandler parameters
The EventHandler attribute accepts a couple parameters.
**Priority** - indicates the priority. There are six different priorities, in order of execution:
- `Lowest`
- `Low`
- `Normal` (Default)
- `High`
- `Highest`
- `Monitor`
These constants refer to the \ref Minecraft.Server.FourKit.Event.EventPriority "EventPriority" enum.
<b><span style="color: red;">Note:</span> The Monitor priority should only be used for reading only. This priority is useful for logging plugins to see the results of an event and modifying values may interfere with those types of plugins.</b>
**IgnoreCancelled** - A boolean which indicates whether or not your listener should fire if the event has been cancelled before it is the listener's turn to handle the event. False by default.
Example:
```csharp
using Minecraft.Server.FourKit.Event;
using Minecraft.Server.FourKit.Event.Player;
public class MyListener : Listener
{
// executes before the second method because it has a much lower priority.
[EventHandler(Priority = EventPriority.Lowest)]
public void onPlayerChat1(PlayerChatEvent e) {
event.setCancelled(true);
}
// Will not execute unless another listener with a lower priority has uncancelled the event.
[EventHandler(Priority = EventPriority.Highest, IgnoreCancelled = true)]
public void onPlayerChat2(PlayerChatEvent e) {
Console.WriteLine("This should not execute.");
}
}
```
@section commands Creating Commands
A big thing you will probably want to do is learn how to create commands.
They are not like bukkit, you dont fill out a yml file.
### Creating our Command class
Lets start by creating our actual command handler. You must have a class that extends the \ref Minecraft.Server.FourKit.Command.CommandExecutor "CommandExecutor" class.
```csharp
public class CoolCommand : CommandExecutor
{
public bool onCommand(CommandSender sender, Command command, string label, string[] args)
{
return true;
}
}
```
`sender` is the actual command sender. This can be either a \ref Minecraft.Server.FourKit.Entity.Player "Player" or a \ref Minecraft.Server.FourKit.Command.ConsoleCommandSender "ConsoleCommandSender"
`command` is the actual command.
`label` is the command name they used to execute.
`args` is the command arguments passed.
You might notice that the `onCommand` func returns a boolean. This indicates if the command executed successfully.
### Registering the command
Now, lets actually register this command. To register the command, you have to use `FourKit.getCommand("command").setExecutor()`
```csharp
public void onEnable()
{
FourKit.getCommand("cool").setExecutor(new CoolCommand());
}
```
Now we can run the command by running `/cool` in chat or typing `cool` in console!
getCommand() returns a \ref Minecraft.Server.FourKit.Command.PluginCommand "PluginCommand" class. You can see all the functions you can use from here!
Now, when we run `help` in console, we should see this:
```
[2026-03-20 23:31:19.462][INFO][console] Plugin commands:
[2026-03-20 23:31:19.463][INFO][console] /cool
```
### Defining usage and description for the command
We can even add a description and define usage to the command!
```csharp
FourKit.getCommand("cool").setDescription("my awesome command");
FourKit.getCommand("cool").setUsage("/cool <arg1>");
```
Now it shows this:
```
[2026-03-20 23:38:06.470][INFO][console] Plugin commands:
[2026-03-20 23:38:06.470][INFO][console] /cool <arg1> - my awesome command
```
### Checking who is running the command
Now that we can do all this, we can check who is running the command. Best way to do this is check if the sender is an instance of \ref Minecraft.Server.FourKit.Entity.Player "Player" or \ref Minecraft.Server.FourKit.Command.ConsoleCommandSender "ConsoleCommandSender".
```csharp
public bool onCommand(CommandSender sender, Command command, string label, string[] args)
{
if (sender is ConsoleCommandSender)
{
// sender is console.
sender.sendMessage("Whats good console");
return true;
}
// sender is player
Player p = (Player)sender;
p.sendMessage("Do it work?");
return true;
}
```
When console runs this command, they will see "Whats good console" in console. When a Player runs this command, they will see "Do it work?" in chat.
From there on, you can do whatever you want in the command. You can modify the player, such as teleport them somewhere. You can do whatever you want!
@section dependencies Dependencies
Say you want to make a plugin that links a Discord bot to your plugin. This is possible! You can use something like [Discord.NET](https://docs.discordnet.dev/) for that.
When a plugin needs dependencies, you also need to bring over the DLL's for the dependencies.
You can put them into a folder under the plugins folder next to the main plugin dll.
Example folder structure:
- plugins/
- plugins/MyPlugin/MyPlugin.dll
- plugins/MyPlugin/CoolDependency.dll
When a plugin folder is made, make sure the main dll matches the name of the folder, or else it will skip it.
You can also avoid this by using [Fody Costura](https://github.com/Fody/Costura) and bundle the dependencies into your DLL.
### Fody Costura
Fody Costura isnt very well documented, but heres the general usage guide that has worked for users:
<!-- why doesnt doxygen support 1. 2. 3. etc? -->
<ol>
<li>
Install Fody Costura
<p>This can be through NuGet, or through anything you wish to use for getting dependencies.</p>
</li>
<li>
Create a FodyWeavers.xml in your project root:
```xml
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura>
<ExcludeAssemblies>
Minecraft.Server.FourKit
</ExcludeAssemblies>
</Costura>
</Weavers>
```
<p>This will exclude bundling fourkit in the DLL too.</p>
</li>
<li>
Make csproj copy dependency DLL files over to build dir
<p>You can do this by adding <code>&lt;CopyLocalLockFileAssemblies&gt;true&lt;/CopyLocalLockFileAssemblies&gt;</code> to the property group.</p>
</li>
<li>
Build
<p>After you've done all this, it should build and put all dependencies into one DLL in your output folder.</p>
</li>
</ol>