Skip to content

Plugin Development

Developer edited this page Jun 22, 2026 · 1 revision

Plugin Development

AquariusProxy's automation modules are native built-ins, but the proxy also keeps ZenithProxy's plugin system: drop-in jars that add your own modules and commands without rebuilding the proxy.

🧩 Template repo: aquariusnetwork9/aquariusproxy-plugin-template — a working example plugin you can clone and build. This page is the companion guide.


Installing a plugin

Plugins are supported on the java release channel only. The native (linux) GraalVM build can't load jars at runtime and will log a warning instead.

  1. Put the plugin jar in the plugins folder next to the AquariusProxy launcher. It's created automatically on first launch.
  2. Restart AquariusProxy. Plugins are loaded once at startup — there is no hot-reload.
  3. A plugin's config (if it has one) is written to plugins/config/<plugin_id>.json.

Run the plugins command in the proxy console to list what loaded.


What a plugin can do

A plugin registers the same building blocks the proxy uses internally:

  • Modules — toggleable units that listen to events, run on the client tick loop, and register packet handlers (inbound from the player, outbound to the server, in either direction).
  • Commands — Brigadier commands usable from the terminal, in-game chat, and Discord, with embed responses.
  • Configs — JSON-backed POJOs that load and save automatically.

Modules and commands are written exactly as they are in the AquariusProxy source — the best reference is the proxy's own code:


Quick start

# 1. Clone the template (or hit "Use this template" on GitHub)
git clone https://github.com/aquariusnetwork9/aquariusproxy-plugin-template
cd aquariusproxy-plugin-template

# 2. Provide the AquariusProxy API jar (the same jar the launcher runs)
gh release download --repo aquariusnetwork9/AquariusProxy --pattern 'AquariusProxy.jar' --dir libs
#   or: cp /path/to/install/AquariusProxy.jar libs/AquariusProxy.jar

# 3. Build
./gradlew build      # Windows: gradlew.bat build

The plugin jar lands in build/libs/. Copy it into your proxy's plugins folder and restart.

Prerequisites

  • JDK 25 — the same JDK AquariusProxy is built with. It's required because compilation runs AquariusProxy's bundled annotation processor. The Gradle toolchain auto-provisions it if it's missing.
  • The AquariusProxy fat jar (AquariusProxy.jar) to compile against. That's how you get the API — AquariusProxy is not published to a public Maven repository, so you supply the jar the launcher already runs. The template looks for it at libs/AquariusProxy.jar, overridable with -Paquarius_jar=/path/to/AquariusProxy.jar.

Plugin anatomy

Entrypoint

Every plugin has one main class that implements AquariusProxyPlugin and carries the @Plugin annotation:

import com.aquarius.plugin.api.AquariusProxyPlugin;
import com.aquarius.plugin.api.Plugin;
import com.aquarius.plugin.api.PluginAPI;

@Plugin(
    id = "my-plugin",                 // lowercase letters, numbers, dashes; must start with a letter
    version = "1.0.0",
    description = "My AquariusProxy plugin",
    authors = {"you"},
    mcVersions = {"1.21.4"}           // or "*" for any MC version
)
public class MyPlugin implements AquariusProxyPlugin {
    @Override
    public void onLoad(PluginAPI api) {
        MyConfig config = api.registerConfig("my-plugin", MyConfig.class);
        api.registerModule(new MyModule());
        api.registerCommand(new MyCommand());
    }
}

The @Plugin annotation is read at build time by an annotation processor that generates the zenithproxy.plugin.json metadata file the proxy loads — you never write that file by hand.

PluginAPI

onLoad receives a PluginAPI:

Method Purpose
registerConfig(fileName, ConfigClass.class) Loads/creates a JSON config and returns the live instance. Stored at plugins/config/<fileName>.json.
registerConfig(fileName, ConfigClass.class, serializer) Same, with a custom serializer (NBT/YAML/TOML/etc.).
registerModule(module) Registers a Module.
registerCommand(command) Registers a Command.
getLogger() A ComponentLogger scoped to your plugin.
getPluginInfo() The data from your @Plugin annotation.

Config

A config is a plain POJO with public, mutable fields. Nested static classes become nested JSON objects. It is saved/loaded automatically (on command execution, proxy start/stop, etc.):

public class MyConfig {
    public boolean enabled = true;
    public int delayTicks = 250;
}

Module

A Module can gate itself on a config flag, subscribe to events, and (optionally) register packet handlers:

public class MyModule extends Module {
    private final Timer timer = Timers.tickTimer();

    @Override public boolean enabledSetting() { return MyPlugin.CONFIG.enabled; }

    @Override public List<EventConsumer<?>> registerEvents() {
        return List.of(of(ClientBotTick.class, this::onTick));
    }

    private void onTick(ClientBotTick event) {
        if (timer.tick(MyPlugin.CONFIG.delayTicks)) info("tick!");
    }
}

The example plugin also shows a packet-handling module (outbound entity-metadata edit for a glowing-ESP effect) and a pathfinding module (driving Baritone to wander). Note packet classes change between MC versions, so packet-handling plugins generally need a separate build per MC version.

Command

Commands are Brigadier-based, identical to the proxy's own:

public class MyCommand extends Command {
    @Override public CommandUsage commandUsage() {
        return CommandUsage.builder()
            .name("myplugin").category(CommandCategory.MODULE)
            .description("Toggle my plugin").usageLines("on/off").build();
    }

    @Override public LiteralArgumentBuilder<CommandContext> register() {
        return command("myplugin").then(argument("toggle", toggle()).executes(c -> {
            MyPlugin.CONFIG.enabled = getToggle(c, "toggle");
            MODULE.get(MyModule.class).syncEnabledFromConfig();   // apply the toggle
            c.getSource().getEmbed().title("My Plugin " + toggleStrCaps(MyPlugin.CONFIG.enabled));
        }));
    }
}

Building & testing

./gradlew build      # plugin jar -> build/libs/<plugin_name>.jar
./gradlew run        # launch AquariusProxy with the plugin loaded in ./run

run installs your freshly built jar into run/plugins and starts the proxy from ./run; the first run walks you through normal proxy setup. You can equally just copy the built jar into an existing install's plugins folder and restart.

New-plugin checklist

  1. Edit gradle.propertiesplugin_name, plugin_id, mc, maven_group.
  2. Move sources from org.example to your own package (under both src/main/java and src/main/templates). IntelliJ's refactor handles imports.
  3. Update the @Plugin annotation (or replace ExamplePlugin with your own main class).

How the template build works

Unlike rfresh2's upstream example, the template does not use a *.plugin.dev Gradle convention plugin (there is no AquariusProxy equivalent). It's self-contained:

  • It compiles compileOnly and annotationProcessor against the AquariusProxy fat jar. That single jar bundles every API and transitive class plus the @Plugin annotation processor (auto-registered via SPI), so there's no Maven publishing or transitive dependency resolution to set up.
  • BuildConstants is generated from src/main/templates by a Gradle Copy/expand task.
  • The Shadow plugin builds the final jar and can bundle/relocate any extra dependencies you declare with implementation(...).

Porting a ZenithProxy plugin

A plugin jar built for ZenithProxy will not load on AquariusProxy as-is. AquariusProxy renamed the whole API package com.zenith.*com.aquarius.* (including ZenithProxyPluginAquariusProxyPlugin), so a compiled ZenithProxy plugin references classes that don't exist here, and the loader requires AquariusProxyPlugin.

Porting is mechanical, because the API shape is identical:

  1. Replace imports com.zenith.com.aquarius. (and ZenithProxyPluginAquariusProxyPlugin).
  2. Compile against AquariusProxy.jar instead of ZenithProxy — the template already does.

External imports (com.github.rfresh2.*, com.mojang.brigadier.*, org.geysermc.mcprotocollib.*, net.kyori.*) are unchanged.


Troubleshooting

Symptom Cause / fix
AquariusProxy API jar not found Put AquariusProxy.jar at libs/AquariusProxy.jar or pass -Paquarius_jar=....
cannot find symbol for com.aquarius.* The API jar is missing or is an older version than your mc target — supply the matching AquariusProxy.jar.
Compile fails with "release 21 not supported" / toolchain errors Use JDK 25 (the toolchain auto-provisions it if your machine allows downloads).
Plugin jar isn't loaded at startup You're on the linux/native channel (use the java jar), the jar isn't in plugins/, or it doesn't implement AquariusProxyPlugin. Check the console for plugin-loader errors.
No zenithproxy.plugin.json in the built jar The @Plugin-annotated class is missing or the API jar wasn't on the annotationProcessor path — rebuild after fixing the jar path.

See also: Installation · Command Reference · The ShiftClick fix

Clone this wiki locally