Repository navigation
Writing Plugins
This page explains how to write your own plugin for MineAlphaProxy using the MineAlpha Plugin API.
MineAlphaProxy loads plugins from two folders:
| Folder | API |
|---|---|
MineAlphaPlugins/ |
Own MineAlpha API (com.minealphaproxy.api) |
VelocityPlugins/ |
Compatibility layer for Velocity plugins |
This page covers the MineAlpha API. If you have an existing Velocity plugin, just drop it into VelocityPlugins/ and it will load.
MyPlugin/
+-- build.gradle.kts
+-- settings.gradle.kts
+-- src/main/
+-- java/com/example/myplugin/
| +-- MyPlugin.java
+-- resources/
+-- minealpha-plugin.yml
rootProject.name = "MyPlugin"plugins {
java
}
group = "com.example"
version = "1.0.0"
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
repositories {
mavenCentral()
}
dependencies {
// Path to your MineAlphaProxy JAR (built from source, or downloaded from Releases).
// Replace the path below with the actual location on your machine.
compileOnly(files("PATH_TO_MINEALPHAPROXY_JAR"))
compileOnly("net.kyori:adventure-api:4.24.0")
compileOnly("org.slf4j:slf4j-api:2.0.13")
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
options.release.set(17)
}
tasks.jar {
archiveFileName.set("MyPlugin-1.0.0.jar")
}Replace PATH_TO_MINEALPHAPROXY_JAR with the full path to your MineAlphaProxy JAR.
Common locations:
| OS | Example path |
|---|---|
| Windows | D:/MineAlphaProxy/build/libs/MineAlphaProxy-1.0.1-ALPHA.jar |
| Linux | /home/user/MineAlphaProxy/build/libs/MineAlphaProxy-1.0.1-ALPHA.jar |
| macOS | /Users/user/MineAlphaProxy/build/libs/MineAlphaProxy-1.0.1-ALPHA.jar |
Use forward slashes / even on Windows - Gradle accepts them and they work cross-platform.
Recommended for distribution: place the MineAlphaProxy JAR inside your plugin project's libs/ folder and use a relative path:
compileOnly(files("libs/MineAlphaProxy-1.0.1-ALPHA.jar"))This way, anyone who clones your plugin repository can build it without editing paths.
compileOnly tells Gradle: "Use this JAR to compile my code, but do not bundle it into my plugin's output JAR."
This is important because:
- MineAlphaProxy's API classes (
MineAlphaPlugin,MineAlphaPlayer, etc.) are already present in the running proxy - Bundling them into your plugin would create duplicate classes and cause
LinkageErrorat runtime - It keeps your plugin JAR small (a few KB instead of tens of MB)
Use compileOnly for anything the proxy already provides:
- The MineAlphaProxy JAR itself
- Adventure API (chat components)
- SLF4J (logging)
Use implementation only for libraries that are not part of the proxy and must be bundled with your plugin.
Place this file in src/main/resources/:
id: myplugin
name: MyPlugin
version: 1.0.0
author: YourName
description: A short description of what this plugin does
main: com.example.myplugin.MyPlugin| Key | Required | Description |
|---|---|---|
id |
yes | Unique plugin id - used as folder name for data |
name |
no | Display name |
version |
no | Plugin version |
author |
no | Author |
description |
no | Description |
main |
yes | Fully qualified main class |
The main class extends MineAlphaPlugin and overrides lifecycle methods.
package com.example.myplugin;
import com.minealphaproxy.api.MineAlphaPlugin;
import com.minealphaproxy.api.annotation.MineAlphaPluginInfo;
@MineAlphaPluginInfo(
id = "myplugin",
name = "MyPlugin",
version = "1.0.0",
author = "YourName"
)
public class MyPlugin extends MineAlphaPlugin {
@Override
public void onLoad() {
getLogger().info("Loading...");
}
@Override
public void onEnable() {
getLogger().info("Enabled!");
}
@Override
public void onDisable() {
getLogger().info("Disabled.");
}
}| Method | When | Use for |
|---|---|---|
onLoad() |
Before onEnable, before events fire |
Reading config, early setup |
onEnable() |
After all plugins loaded | Registering commands and events |
onDisable() |
On proxy shutdown | Saving data, closing resources |
From onEnable() onward, getProxy() returns a MineAlphaProxy instance.
MineAlphaProxy proxy = getProxy();
// Players
Collection<MineAlphaPlayer> all = proxy.getPlayers();
Optional<MineAlphaPlayer> steve = proxy.getPlayer("Steve");
int count = proxy.getPlayerCount();
// Servers
Collection<MineAlphaServer> servers = proxy.getServers();
Optional<MineAlphaServer> lobby = proxy.getServer("lobby");
// Broadcast
proxy.broadcastMessage("§aServer restart in 5 minutes!");
proxy.broadcastMessage(Component.text("Hello"));MineAlphaPlayer player = ...;
String name = player.getName();
UUID uuid = player.getUniqueId();
InetSocketAddress address = player.getAddress();
boolean online = player.isOnline();
// Messages
player.sendMessage("§aHello!");
player.sendMessage(Component.text("Hello!", NamedTextColor.GREEN));
player.sendActionBar("§eNow playing: lobby");
// Kick
player.kick("§cYou have been kicked.");
// Permissions (always returns true in 1.0.1-ALPHA)
boolean has = player.hasPermission("myplugin.admin");
// Server (returns "unknown" in 1.0.1-ALPHA)
String server = player.getCurrentServer();Commands extend MineAlphaCommand and are registered through the command manager.
import com.minealphaproxy.api.command.CommandSource;
import com.minealphaproxy.api.command.MineAlphaCommand;
public final class HelloCommand extends MineAlphaCommand {
public HelloCommand() {
super(
"hello", // name
"Say hello", // description
"/hello [name]", // usage
"myplugin.hello", // permission (null = none)
"hi", "hey" // aliases
);
}
@Override
public void execute(CommandSource sender, String[] args) {
String target = args.length > 0 ? args[0] : sender.getName();
sender.sendMessage("§aHello, §e" + target + "§a!");
}
}Register in onEnable():
@Override
public void onEnable() {
getProxy().getCommandManager().register(new HelloCommand());
getLogger().info("Registered /hello");
}Unregister by name:
getProxy().getCommandManager().unregister("hello");CommandSource source = ...;
source.sendMessage("§aMessage");
source.getName(); // "CONSOLE" or player name
source.hasPermission("perm.node"); // always true in 1.0.1-ALPHA
source.isPlayer(); // true if a player ran the command
source.asPlayer(); // Optional<MineAlphaPlayer>Example using asPlayer():
@Override
public void execute(CommandSource sender, String[] args) {
if (!sender.isPlayer()) {
sender.sendMessage("§cOnly players can use this.");
return;
}
MineAlphaPlayer player = sender.asPlayer().get();
player.sendActionBar("§eYou did it!");
}Annotate methods with @Subscribe and the single argument is the event type.
import com.minealphaproxy.api.annotation.Subscribe;
import com.minealphaproxy.api.event.PlayerJoinEvent;
import com.minealphaproxy.api.event.PlayerLeaveEvent;
import com.minealphaproxy.api.event.ProxyStartEvent;
import com.minealphaproxy.api.event.ProxyStopEvent;
@Subscribe
public void onProxyStart(ProxyStartEvent event) {
getLogger().info("Proxy started");
}
@Subscribe
public void onJoin(PlayerJoinEvent event) {
MineAlphaPlayer player = event.getPlayer();
player.sendMessage("§aWelcome, " + player.getName() + "!");
}
@Subscribe
public void onLeave(PlayerLeaveEvent event) {
getLogger().info("{} left", event.getPlayer().getName());
}
@Subscribe
public void onStop(ProxyStopEvent event) {
getLogger().info("Goodbye");
}| Event | When |
|---|---|
ProxyStartEvent |
After all plugins loaded, proxy is listening |
ProxyStopEvent |
Before shutdown |
PlayerJoinEvent |
After a player successfully logs in |
PlayerLeaveEvent |
When a player disconnects |
PlayerChatEvent |
(planned, not fired yet in 1.0.1-ALPHA) |
ServerSwitchEvent |
(planned, not fired yet in 1.0.1-ALPHA) |
Event priority (via @Subscribe(priority = N), higher runs first) is supported by the API but not yet used by most events.
Run tasks in the background:
getProxy().getScheduler().runAsync(() -> {
getLogger().info("Hello from a task");
});
getProxy().getScheduler().runAsyncLater(() -> {
getLogger().info("Hello 5 seconds later");
}, 5, TimeUnit.SECONDS);
getProxy().getScheduler().runAsyncRepeating(() -> {
getLogger().info("Tick - {} players online",
getProxy().getPlayerCount());
}, 0, 60, TimeUnit.SECONDS);Cancel a task:
var task = getProxy().getScheduler().runAsyncRepeating(...);
task.cancel();Each plugin gets its own folder under MineAlphaPlugins/<id>/.
File folder = getDataFolder();
File configFile = new File(folder, "config.yml");Save a bundled resource:
// Copies src/main/resources/config.yml to your data folder
saveResource("config.yml", false);Read the YAML with SnakeYAML (already available):
try (InputStream in = new FileInputStream(
new File(getDataFolder(), "config.yml"))) {
Map<String, Object> map = new Yaml().load(in);
String greeting = (String) map.getOrDefault("greeting", "Hello");
}A small plugin that greets players, adds a /hello command, and logs player count every minute.
package com.example.myplugin;
import com.minealphaproxy.api.MineAlphaPlugin;
import com.minealphaproxy.api.MineAlphaPlayer;
import com.minealphaproxy.api.annotation.MineAlphaPluginInfo;
import com.minealphaproxy.api.annotation.Subscribe;
import com.minealphaproxy.api.command.CommandSource;
import com.minealphaproxy.api.command.MineAlphaCommand;
import com.minealphaproxy.api.event.PlayerJoinEvent;
import com.minealphaproxy.api.event.ProxyStartEvent;
import java.util.concurrent.TimeUnit;
@MineAlphaPluginInfo(
id = "myplugin",
name = "MyPlugin",
version = "1.0.0",
author = "YourName"
)
public class MyPlugin extends MineAlphaPlugin {
@Override
public void onEnable() {
getLogger().info("MyPlugin enabled!");
getProxy().getCommandManager().register(new MineAlphaCommand(
"hello", "Say hello", "/hello [name]", null, "hi") {
@Override
public void execute(CommandSource sender, String[] args) {
String target = args.length > 0 ? args[0] : sender.getName();
sender.sendMessage("§aHello, §e" + target + "§a!");
}
});
getProxy().getScheduler().runAsyncRepeating(() -> {
getLogger().info("Online: {} players",
getProxy().getPlayerCount());
}, 0, 60, TimeUnit.SECONDS);
}
@Override
public void onDisable() {
getLogger().info("MyPlugin disabled");
}
@Subscribe
public void onStart(ProxyStartEvent event) {
getLogger().info("Proxy is up!");
}
@Subscribe
public void onJoin(PlayerJoinEvent event) {
MineAlphaPlayer player = event.getPlayer();
player.sendMessage("§aWelcome, " + player.getName() + "!");
}
}cd MyPlugin
./gradlew clean buildOutput: build/libs/MyPlugin-1.0.0.jar
Copy to the proxy:
MineAlphaPlugins/MyPlugin-1.0.0.jar
Restart MineAlphaProxy. Expected log:
[..] INFO - Loaded MineAlpha plugin: myplugin v1.0.0
[..] INFO - Registered command: /hello
[..] INFO - MyPlugin enabled!
| Interface | Purpose |
|---|---|
MineAlphaProxy |
Main API - players, servers, commands, scheduler, events |
MineAlphaPlayer |
A connected player |
MineAlphaServer |
A backend server |
MineAlphaLogger |
Plugin logger (info, warn, error) |
MineAlphaScheduler |
Async task scheduler |
MineAlphaEventBus |
Event dispatch (used internally) |
MineAlphaCommandManager |
Command registry |
CommandSource |
Source of a command (console or player) |
See the Installation page for the proxy JAR location used for compileOnly.
- Permission checks always return
true- no Permissions API yet -
getCurrentServer()returns"unknown" -
PlayerChatEventandServerSwitchEventexist but are not fired -
Player.sendMessage(Component)works reliably only on 1.20.2 and older; on newer versions NBT-based chat is required (planned) - Velocity plugin support is partial - complex plugins may fail on missing API classes
- MineAlphaProxyTools - example of a complete plugin
-
Configuration -
core.ymlreference - Troubleshooting - fixing load errors