Reference Bukkit/Paper plugin for ac.grim.grimac:GrimAPI:1.3.2.1.
This project shows the current event-channel API, the cross-platform
GrimPlugin owner pattern, and the deprecated listener paths kept for older
plugins that are migrating forward.
./gradlew buildThe jar is written to:
build/libs/Grim-Example-API-Plugin-1.0.0.jar
The project uses Java 17 and compiles against paper-api:1.20.4-R0.1-SNAPSHOT.
The example only uses stable JavaPlugin and logging APIs, so the built jar is
not tied to one Paper runtime version.
Put the jar in plugins/ next to GrimAC and restart the server:
plugins/
├── GrimAC-<version>.jar
└── Grim-Example-API-Plugin-1.0.0.jar
plugin.yml declares depend: [GrimAC], so this plugin enables after Grim has
created the public API and event bus.
Resolve Grim once, then subscribe through typed channels:
GrimAbstractAPI api = GrimAPIProvider.get();
EventBus bus = api.getEventBus();
GrimPlugin grim = api.getGrimPlugin(this);
bus.get(GrimTransactionSendEvent.class).onTransactionSend(grim, (user, id, ts) -> {
getLogger().info("tx-send " + id + " for " + user.getName());
});
bus.get(FlagEvent.class).onFlag(grim, (user, check, verbose, cancelled) -> {
if (shouldSuppress(user, check)) return true;
return cancelled;
});Cancellable handlers return the next cancelled state. Return the incoming
cancelled value when you only want to observe.
Do not manually construct BasicGrimPlugin for normal plugins. Pass the native
owner object to api.getGrimPlugin(...) and let Grim resolve it.
| Platform | Owner object |
|---|---|
| Bukkit / Spigot / Paper | JavaPlugin / Bukkit Plugin, usually this |
| Fabric | ModInitializer, ModContainer, or the mod id string |
| Shared module | A Class<?> from your plugin/mod |
That lets you keep Grim-facing code in a shared class:
public final class GrimHooks {
public static void register(Object owner) {
GrimAbstractAPI api = GrimAPIProvider.get();
GrimPlugin grim = api.getGrimPlugin(owner);
EventBus bus = api.getEventBus();
bus.get(FlagEvent.class).onFlag(grim, (user, check, verbose, cancelled) -> {
grim.getLogger().info(user.getName() + " flagged " + check.getCheckName());
return cancelled;
});
}
}Bukkit/Paper:
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
GrimHooks.register(this);
}
}Fabric:
public final class MyMod implements ModInitializer {
@Override
public void onInitialize() {
GrimHooks.register(this);
}
}Subscribe to concrete channels when you need all fields. Subscribe to abstract channels when one handler should observe a whole family:
GrimCheckEvent.Channel checks =
(GrimCheckEvent.Channel) bus.get(GrimCheckEvent.class);
checks.onCheck(grim, (user, check, cancelled) -> cancelled);
GrimVerboseCheckEvent.Channel verboseChecks =
(GrimVerboseCheckEvent.Channel) bus.get(GrimVerboseCheckEvent.class);
verboseChecks.onVerboseCheck(grim, (user, check, verbose, cancelled) -> cancelled);
GrimSetbackEvent.Channel setbacks =
(GrimSetbackEvent.Channel) bus.get(GrimSetbackEvent.class);
setbacks.onAnySetback(grim, (user, ts) ->
getLogger().info("setback " + user.getName()));
GrimEvent.Channel allEvents = (GrimEvent.Channel) bus.get(GrimEvent.class);
allEvents.onAnyEvent(grim, (eventClass, cancelled) ->
getLogger().fine(eventClass.getSimpleName()));GrimPlayerSetbackEvent includes the teleport id and target position.
GrimVehicleSetbackEvent includes the vehicle target position.
GrimSetbackEvent bridges both when you only need the semantic setback signal.
Handlers run in ascending priority order. Lower priority runs first; higher priority runs later and gets the final say on cancellation.
bus.get(FlagEvent.class)
.onFlag(grim, handler, 100, false);ignoreCancelled = false means the handler is skipped after an earlier handler
cancels the event. ignoreCancelled = true means it still runs and receives
the current cancelled state.
This is the Bukkit priority direction. If you are migrating from pre-1.3 Grim, review any explicit priority numbers.
These still work for source compatibility, but new code should use typed channels:
@SuppressWarnings("deprecation")
bus.get(FlagEvent.class).onFlag((Object) this, (user, check, verbose, cancelled) -> {
return cancelled;
});
bus.registerAnnotatedListeners(this, new Listener());
public final class Listener {
@GrimEventHandler
public void onJoin(GrimJoinEvent event) {
// legacy reflective listener
}
}Use bus.unregisterAllListeners(this) or bus.unregisterAllListeners(grim) on
disable if you want explicit cleanup. Grim also cleans up plugin-bound handlers
as part of the plugin lifecycle.
This plugin demonstrates:
- Transaction send and receive
- Teleport
- Player and vehicle setbacks
- Join, quit, and reload
- Flag, complete prediction, and command execute
- Check, verbose-check, setback-family, and root event observers
- Deprecated object-context and
@GrimEventHandlerlistener paths
Storage, history, and verdict APIs are not demonstrated here. Those are separate extension points for datastore, dashboard, and verdict-engine integrations.