### Obtaining the API instance with GrimAPIProvider
Source: https://context7.com/grimanticheat/grimapi/llms.txt
This snippet shows how to get an instance of the GrimAbstractAPI using GrimAPIProvider. The `get()` method is synchronous and throws an exception if Grim is not loaded, while `getAsync()` provides a non-blocking way to obtain the API.
```APIDOC
## GrimAPIProvider — Obtaining the API instance
Static singleton accessor for `GrimAbstractAPI`. Use `get()` synchronously at plugin enable time (after Grim has loaded); use `getAsync()` when your plugin may load before Grim.
```java
import ac.grim.grimac.api.GrimAPIProvider;
import ac.grim.grimac.api.GrimAbstractAPI;
public class MyPlugin extends JavaPlugin {
private GrimAbstractAPI grimAPI;
@Override
public void onEnable() {
// Synchronous — throws IllegalStateException if Grim is not loaded
grimAPI = GrimAPIProvider.get();
getLogger().info("Grim version: " + grimAPI.getGrimVersion());
}
}
// Async variant — safe if load order is uncertain
GrimAPIProvider.getAsync().thenAccept(api -> {
// runs when API is ready, even if Grim loads after your plugin
getLogger().info("Grim loaded: " + api.getGrimVersion());
}).exceptionally(ex -> {
getLogger().severe("Grim failed to load: " + ex.getMessage());
return null;
});
```
```
--------------------------------
### Add GrimAPI Dependency with Maven
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Configure your Maven project to use the GrimAPI library. Remember to substitute %VERSION% with the current API version. This example shows how to add the repository and the dependency.
```xml
grimac-snapshots
GrimAC's Maven Repository
https://repo.grim.ac/snapshots
ac.grim.grimac
GrimAPI
%VERSION%
provided
```
--------------------------------
### Get GrimAPI Instance (Synchronous)
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Obtain the GrimAPI instance synchronously using GrimAPIProvider.get(). This method throws an IllegalStateException if Grim has not loaded yet. Use this when your plugin is guaranteed to load after Grim.
```java
import ac.grim.grimac.api.GrimAPIProvider;
import ac.grim.grimac.api.GrimAbstractAPI;
public class MyPlugin extends JavaPlugin {
private GrimAbstractAPI grimAPI;
@Override
public void onEnable() {
// Synchronous — throws IllegalStateException if Grim is not loaded
grimAPI = GrimAPIProvider.get();
getLogger().info("Grim version: " + grimAPI.getGrimVersion());
}
}
```
--------------------------------
### BackendRegistry - Registering Custom Storage Backends
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Allows third-party plugins to contribute their own BackendProvider to Grim's storage pipeline before the data store starts. This must be called during plugin load or before Grim finishes enabling.
```APIDOC
## `BackendRegistry` — Registering custom storage backends (Experimental)
Allows third-party plugins to contribute their own `BackendProvider` to Grim's storage pipeline before the data store starts. Must be called during plugin load or before Grim finishes enabling.
```java
import ac.grim.grimac.api.storage.backend.*;
// --- Custom backend provider implementation ---
public class MyBackendProvider implements BackendProvider {
@Override public String id() { return "mybackend"; }
@Override public Class extends BackendConfig> configType() { return MyBackendConfig.class; }
@Override
public BackendConfig readConfig(BackendConfigSource source) {
return new MyBackendConfig(source.getString("url", "http://localhost:8080"));
}
@Override
public Backend create(BackendConfig config) {
return new MyBackend((MyBackendConfig) config);
}
}
// --- Register at plugin enable (before Grim data store starts) ---
BackendRegistry registry = GrimAPIProvider.get().getBackendRegistry();
registry.register(new MyBackendProvider());
// Inspect currently registered backend IDs
Set ids = registry.registeredIds();
getLogger().info("Available backends: " + ids);
// Example output: [sqlite, mysql, postgres, mongo, redis, mybackend]
```
```
--------------------------------
### Get GrimAPI Instance (Asynchronous)
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Safely obtain the GrimAPI instance asynchronously using GrimAPIProvider.getAsync(). This is useful when your plugin's load order relative to Grim is uncertain. The callback runs once the API is ready.
```java
// Async variant — safe if load order is uncertain
GrimAPIProvider.getAsync().thenAccept(api -> {
// runs when API is ready, even if Grim loads after your plugin
getLogger().info("Grim loaded: " + api.getGrimVersion());
}).exceptionally(ex -> {
getLogger().severe("Grim failed to load: " + ex.getMessage());
return null;
});
```
--------------------------------
### Core API facade with GrimAbstractAPI
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Demonstrates the usage of GrimAbstractAPI for core functionalities like looking up players, registering custom variables, triggering config reloads, and checking the API's startup status.
```APIDOC
## `GrimAbstractAPI` — Core API facade
Central API object. Provides access to the event bus, user lookup, variable registration, alert manager, config manager, and the storage backend registry.
```java
GrimAbstractAPI api = GrimAPIProvider.get();
// Look up a tracked player by UUID
UUID uuid = player.getUniqueId();
GrimUser user = api.getGrimUser(uuid); // null if not tracked
if (user != null) {
getLogger().info(user.getName() + " ping: " + user.getTransactionPing() + "ms");
}
// Register a custom message variable: %myserver%
api.registerVariable("%myserver%", "SurvivalNetwork");
// Register a per-player variable: %vl%
api.registerVariable("%vl%", grimUser -> {
double maxVL = grimUser.getChecks().stream()
.mapToDouble(c -> c.getViolations())
.max().orElse(0);
return String.valueOf((int) maxVL);
});
// Trigger a config reload asynchronously
api.reloadAsync().thenAccept(success ->
getLogger().info("Grim reload " + (success ? "succeeded" : "failed")));
// Check if Grim has fully started
if (!api.hasStarted()) {
getLogger().warning("Grim has not finished starting yet.");
}
```
```
--------------------------------
### EventBus - Subscribing and Managing Events
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Demonstrates how to retrieve event channels, subscribe to events with custom handlers and priorities, register custom events, and unregister listeners.
```APIDOC
## EventBus - Subscribing and Managing Events
The event bus dispatches Grim events through typed `EventChannel`s. Retrieve a channel with `bus.get(EventClass.class)`, then subscribe via its `onX(grimPlugin, handler)` methods. Handlers fire in ascending priority order (lower number = earlier); higher-priority handlers have final say on cancellation.
```java
GrimAbstractAPI api = GrimAPIProvider.get();
EventBus bus = api.getEventBus();
GrimPlugin grim = api.getGrimPlugin(this);
// --- Subscribe to FlagEvent (cancellable) ---
bus.get(FlagEvent.class).onFlag(grim, (user, check, verbose, cancelled) -> {
// Allow whitelisted players through
if (user.hasPermission("myplugin.bypass")) return false; // un-cancel
getLogger().info(user.getName() + " flagged by " + check.getCheckName()
+ " VL=" + check.getViolations() + " [" + verbose + "]");
return cancelled; // preserve existing cancellation state
});
// --- Subscribe with explicit priority (higher = later, final say) ---
bus.get(FlagEvent.class).onFlag(grim,
(user, check, verbose, cancelled) -> {
if (check.getViolations() > 100) return true; // force cancel at high VL
return cancelled;
},
/*priority*/ 10,
/*ignoreCancelled*/ false
);
// --- Register custom addon event ---
bus.register(MyCustomEvent.class, new MyCustomEvent.Channel());
// --- Cleanup on plugin disable ---
bus.unregisterAllListeners(grim);
```
```
--------------------------------
### Run Safely and Schedule Real-Time Tasks with GrimUser
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Use `runSafely` to execute code on the Netty thread and `addRealTimeTaskNext` to schedule a task that fires once the next transaction is acknowledged. `addRealTimeTaskNow` can be used for convenience to fire a task after the current transaction.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(uuid);
if (user == null) return;
// Run immediately on the Netty thread
user.runSafely(() -> {
int lastSent = user.getLastTransactionSent();
int lastReceived = user.getLastTransactionReceived();
getLogger().info("tx sent=" + lastSent + " received=" + lastReceived);
// Schedule a task to fire once the NEXT transaction is acknowledged
user.addRealTimeTaskNext(() -> {
// Client has caught up to the current server state
getLogger().info(user.getName() + " acknowledged latest tick.");
});
});
// Convenience — fires after current transaction
user.runSafely(() -> user.addRealTimeTaskNow(() ->
getLogger().info("Now-task fired for " + user.getName())));
```
--------------------------------
### Subscribing to Events
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Demonstrates how to subscribe to non-cancellable and cancellable events using the EventBus and GrimPlugin.
```APIDOC
## Subscribing to Events (1.3+)
### Description
Subscribe to events dispatched through `EventChannel`s. Handlers receive event fields directly.
### Method
`EventBus.get(EventClass.class).onEventName(GrimPlugin plugin, Handler handler)`
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```java
GrimAbstractAPI api = GrimAPIProvider.get();
EventBus bus = api.getEventBus();
GrimPlugin grim = api.getGrimPlugin(this);
// Non-cancellable event
bus.get(GrimTransactionSendEvent.class)
.onTransactionSend(grim, (user, id, ts) -> {
getLogger().info("tx-send " + id + " for " + user.getName());
});
// Cancellable event
bus.get(FlagEvent.class)
.onFlag(grim, (user, check, verbose, cancelled) -> {
if (verbose.contains("safe")) return false; // un-cancel
return cancelled;
});
```
### Response
#### Success Response (200)
None (event handlers do not return HTTP responses)
#### Response Example
None
```
--------------------------------
### Plugin context wrapper with GrimPlugin
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Shows how to use `GrimPlugin` to create a platform-agnostic wrapper for your plugin's identity, which is used for managing event subscriptions and ensuring proper cleanup.
```APIDOC
## `GrimPlugin` — Plugin context wrapper
Platform-agnostic wrapper around your plugin/mod identity. Resolve it once at enable and cache it; the bus tracks subscriptions by this object and cleans them up on disable.
```java
import ac.grim.grimac.api.plugin.GrimPlugin;
public class MyPlugin extends JavaPlugin {
private GrimPlugin grimPlugin;
@Override
public void onEnable() {
GrimAbstractAPI api = GrimAPIProvider.get();
// Pass `this` (JavaPlugin) for Bukkit, ModContainer for Fabric, or any Class
grimPlugin = api.getGrimPlugin(this);
getLogger().info("Resolved as: " + grimPlugin.getDescription().getName());
}
@Override
public void onDisable() {
// Clean up all subscriptions registered under grimPlugin
GrimAPIProvider.get().getEventBus().unregisterAllListeners(grimPlugin);
}
}
```
```
--------------------------------
### Register Custom Backend Provider in GrimAPI
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Implement and register a custom BackendProvider to extend Grim's storage capabilities. This must be called during plugin load or before Grim finishes enabling.
```java
import ac.grim.grimac.api.storage.backend.*;
// --- Custom backend provider implementation ---
public class MyBackendProvider implements BackendProvider {
@Override public String id() { return "mybackend"; }
@Override public Class extends BackendConfig> configType() { return MyBackendConfig.class; }
@Override
public BackendConfig readConfig(BackendConfigSource source) {
return new MyBackendConfig(source.getString("url", "http://localhost:8080"));
}
@Override
public Backend create(BackendConfig config) {
return new MyBackend((MyBackendConfig) config);
}
}
// --- Register at plugin enable (before Grim data store starts) ---
BackendRegistry registry = GrimAPIProvider.get().getBackendRegistry();
registry.register(new MyBackendProvider());
// Inspect currently registered backend IDs
Set ids = registry.registeredIds();
getLogger().info("Available backends: " + ids);
// Example output: [sqlite, mysql, postgres, mongo, redis, mybackend]
```
--------------------------------
### GrimJoinEvent / GrimQuitEvent - Player Lifecycle
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Fired asynchronously when Grim begins and ends tracking a player. Use these instead of Bukkit's PlayerJoinEvent / PlayerQuitEvent when the GrimUser object needs to be ready.
```APIDOC
## GrimJoinEvent / GrimQuitEvent - Player lifecycle
Fired asynchronously when Grim begins and ends tracking a player. Use these instead of Bukkit's `PlayerJoinEvent` / `PlayerQuitEvent` when you need the `GrimUser` object to already be ready.
```java
// Track join time per player
Map joinTimes = new ConcurrentHashMap<>();
bus.get(GrimJoinEvent.class).onJoin(grim, user -> {
joinTimes.put(user.getUniqueId(), System.currentTimeMillis());
getLogger().info("Grim now tracking: " + user.getName()
+ " (client=" + user.getVersionName()
+ ", brand=" + user.getBrand() + ")");
});
bus.get(GrimQuitEvent.class).onQuit(grim, user -> {
Long joined = joinTimes.remove(user.getUniqueId());
if (joined != null) {
long sessionMs = System.currentTimeMillis() - joined;
getLogger().info(user.getName() + " session: " + (sessionMs / 1000) + "s");
}
});
```
```
--------------------------------
### Subscribe and Manage Events with EventBus
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Demonstrates subscribing to FlagEvent with default and explicit priorities, registering custom events, and unregistering listeners. Handlers fire in ascending priority order, with lower numbers executing earlier.
```java
GrimAbstractAPI api = GrimAPIProvider.get();
EventBus bus = api.getEventBus();
GrimPlugin grim = api.getGrimPlugin(this);
// --- Subscribe to FlagEvent (cancellable) ---
bus.get(FlagEvent.class).onFlag(grim, (user, check, verbose, cancelled) -> {
// Allow whitelisted players through
if (user.hasPermission("myplugin.bypass")) return false; // un-cancel
getLogger().info(user.getName() + " flagged by " + check.getCheckName()
+ " VL=" + check.getViolations() + " [" + verbose + "]");
return cancelled; // preserve existing cancellation state
});
// --- Subscribe with explicit priority (higher = later, final say) ---
bus.get(FlagEvent.class).onFlag(grim,
(user, check, verbose, cancelled) -> {
if (check.getViolations() > 100) return true; // force cancel at high VL
return cancelled;
},
/*priority*/ 10,
/*ignoreCancelled*/ false
);
// --- Register custom addon event ---
bus.register(MyCustomEvent.class, new MyCustomEvent.Channel());
// --- Cleanup on plugin disable ---
bus.unregisterAllListeners(grim);
```
--------------------------------
### GrimAbstractAPI - Core API Usage
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Access core GrimAPI functionalities like user lookup, variable registration, and configuration reloads. Use GrimAPIProvider.get() to obtain the API instance.
```java
GrimAbstractAPI api = GrimAPIProvider.get();
// Look up a tracked player by UUID
UUID uuid = player.getUniqueId();
GrimUser user = api.getGrimUser(uuid); // null if not tracked
if (user != null) {
getLogger().info(user.getName() + " ping: " + user.getTransactionPing() + "ms");
}
// Register a custom message variable: %myserver%
api.registerVariable("%myserver%", "SurvivalNetwork");
// Register a per-player variable: %vl%
api.registerVariable("%vl%", grimUser -> {
double maxVL = grimUser.getChecks().stream()
.mapToDouble(c -> c.getViolations())
.max().orElse(0);
return String.valueOf((int) maxVL);
});
// Trigger a config reload asynchronously
api.reloadAsync().thenAccept(success ->
getLogger().info("Grim reload " + (success ? "succeeded" : "failed")));
// Check if Grim has fully started
if (!api.hasStarted()) {
getLogger().warning("Grim has not finished starting yet.");
}
```
--------------------------------
### ConfigManager - Reading Grim's configuration
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Provides read-only access to Grim's loaded configuration values, retrievable via `api.getConfigManager()`. Useful for addons that depend on configuration values like punishment thresholds or toggle states.
```APIDOC
## ConfigManager - Reading Grim's configuration
Read-only access to Grim's loaded configuration values. Retrieve via `api.getConfigManager()`. Useful for reading values your addon depends on (e.g., punishment thresholds, toggle states).
```java
ConfigManager cfg = GrimAPIProvider.get().getConfigManager();
if (!cfg.hasLoaded()) {
getLogger().warning("Grim config has not loaded yet.");
return;
}
// Read typed values with fallback defaults
String lang = cfg.getStringElse("language", "en");
boolean logViolations = cfg.getBooleanElse("verbose.print-to-console", false);
int maxVL = cfg.getIntElse("MaxVL", 10);
double decay = cfg.getDoubleElse("decay", 0.05);
List disabledChecks = cfg.getStringListElse("disabled-checks",
Collections.emptyList());
getLogger().info("Language=" + lang
+ " logViolations=" + logViolations
+ " maxVL=" + maxVL
+ " disabledChecks=" + disabledChecks);
```
```
--------------------------------
### Caching Event Channels
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Explains how to cache event channels for performance optimization in hot paths.
```APIDOC
## Caching Event Channels
### Description
Cache event channels using `static final` variables to avoid repeated lookups and improve performance.
### Method
`static final EventChannel CHANNEL_NAME = GrimAPIProvider.get().getEventBus().get(EventClass.class);`
### Parameters
None
### Request Example
```java
public final class MyHotPathClass {
private static final FlagEvent.Channel FLAG =
GrimAPIProvider.get().getEventBus().get(FlagEvent.class);
public void process() {
// No map lookup, no event-object allocation.
FLAG.fire(user, check, verbose);
}
}
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### Registering Addon Events
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Details how plugins can register their own custom event channels.
```APIDOC
## Registering Addon Events
### Description
Plugins can register their custom `GrimEvent` subclasses with the EventBus.
### Method
`bus.register(MyEvent.class, new MyEvent.Channel())`
### Parameters
None
### Request Example
```java
// In plugin's enable method:
bus.register(MyEvent.class, new MyEvent.Channel());
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### Read Grim Configuration Values
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Provides read-only access to Grim's configuration. Useful for addons that depend on specific settings like language, violation logging, or disabled checks.
```java
ConfigManager cfg = GrimAPIProvider.get().getConfigManager();
if (!cfg.hasLoaded()) {
getLogger().warning("Grim config has not loaded yet.");
return;
}
// Read typed values with fallback defaults
String lang = cfg.getStringElse("language", "en");
boolean logViolations = cfg.getBooleanElse("verbose.print-to-console", false);
int maxVL = cfg.getIntElse("MaxVL", 10);
double decay = cfg.getDoubleElse("decay", 0.05);
List disabledChecks = cfg.getStringListElse("disabled-checks",
Collections.emptyList());
getLogger().info("Language=" + lang
+ " logViolations=" + logViolations
+ " maxVL=" + maxVL
+ " disabledChecks=" + disabledChecks);
}
```
--------------------------------
### Add GrimAPI Dependency with Gradle
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Configure your Gradle build to include the GrimAPI library. Ensure you replace %VERSION% with the latest API version. This snippet adds the necessary repository and dependency.
```kotlin
repositories {
maven {
name = "grimacSnapshots"
url = uri("https://repo.grim.ac/snapshots")
}
}
dependencies {
// replace %VERSION% with the latest API version
compileOnly("ac.grim.grimac:GrimAPI:%VERSION%")
}
```
--------------------------------
### Legacy API Methods
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Details the preserved legacy API methods for event posting and subscription.
```APIDOC
## Legacy API Methods
### Description
Legacy methods `bus.post(event)`, `bus.subscribe(ctx, EventClass.class, listener)`, and `@GrimEventHandler` are preserved for compatibility but are deprecated.
### Method
`bus.post(event)`
`bus.subscribe(ctx, EventClass.class, listener)`
`@GrimEventHandler`
### Parameters
None
### Request Example
```java
// Legacy methods are preserved but deprecated.
// bus.post(event);
// bus.subscribe(ctx, EventClass.class, listener);
// @GrimEventHandler
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### Legacy Event Subscription
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Demonstrates the deprecated method of subscribing to events using an Object context.
```APIDOC
## Legacy Event Subscription
### Description
Subscribe to events using a deprecated overload that accepts an `Object` context instead of a `GrimPlugin`.
### Method
`EventBus.get(EventClass.class).onEventName(Object pluginContext, Handler handler)`
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```java
// Works but warns. Call api.getGrimPlugin(this) once and use the
// non-deprecated overload instead.
bus.get(FlagEvent.class).onFlag((Object) this, (u, c, v, cancelled) -> cancelled);
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### Add GrimAPI Dependency (Gradle)
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Add GrimAPI as a compileOnly dependency in your build.gradle.kts file. Ensure the Grim Maven repository is configured.
```kotlin
repositories {
maven {
name = "grimacSnapshots"
url = uri("https://repo.grim.ac/snapshots")
}
}
dependencies {
compileOnly("ac.grim.grimac:GrimAPI:1.3.2.0")
}
```
--------------------------------
### Event Subscription with Priority and IgnoreCancelled
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Shows how to specify priority and control cancellation behavior for event handlers.
```APIDOC
## Event Subscription with Priority and IgnoreCancelled
### Description
Subscribe to events with custom priority and control whether cancelled events are ignored.
### Method
`EventBus.get(EventClass.class).onEventName(GrimPlugin plugin, Handler handler, int priority, boolean ignoreCancelled)`
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```java
// Priority + ignoreCancelled overloads
bus.get(FlagEvent.class)
.onFlag(grim, (u, c, v, cancelled) -> true, /*priority*/ 10, /*ignoreCancelled*/ false);
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### Subscribe to GrimAPI Events
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Subscribe to non-cancellable and cancellable events using the EventBus. Resolve the GrimPlugin once at plugin enable and reuse it. Handlers receive event fields as positional parameters.
```java
GrimAbstractAPI api = GrimAPIProvider.get();
EventBus bus = api.getEventBus();
GrimPlugin grim = api.getGrimPlugin(this); // resolve once; `this` can be a Bukkit
// JavaPlugin, a Fabric ModContainer, etc.
// Non-cancellable — observational
bus.get(GrimTransactionSendEvent.class)
.onTransactionSend(grim, (user, id, ts) -> {
getLogger().info("tx-send " + id + " for " + user.getName());
});
// Cancellable — returns the new cancelled state (true = cancel)
bus.get(FlagEvent.class)
.onFlag(grim, (user, check, verbose, cancelled) -> {
if (verbose.contains("safe")) return false; // un-cancel
return cancelled;
});
// Priority + ignoreCancelled overloads
bus.get(FlagEvent.class)
.onFlag(grim, (u, c, v, cancelled) -> true, /*priority*/ 10, /*ignoreCancelled*/ false);
```
--------------------------------
### Add GrimAPI Dependency (Maven)
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Add GrimAPI as a provided dependency in your pom.xml file. Ensure the Grim Maven repository is configured.
```xml
grimac-snapshots
https://repo.grim.ac/snapshots
ac.grim.grimac
GrimAPI
1.3.2.0
provided
```
--------------------------------
### GrimPlugin - Plugin Context Wrapper
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Create a platform-agnostic GrimPlugin wrapper for your plugin identity. This is used to manage event subscriptions tied to your plugin's lifecycle.
```java
import ac.grim.grimac.api.plugin.GrimPlugin;
public class MyPlugin extends JavaPlugin {
private GrimPlugin grimPlugin;
@Override
public void onEnable() {
GrimAbstractAPI api = GrimAPIProvider.get();
// Pass `this` (JavaPlugin) for Bukkit, ModContainer for Fabric, or any Class
grimPlugin = api.getGrimPlugin(this);
getLogger().info("Resolved as: " + grimPlugin.getDescription().getName());
}
@Override
public void onDisable() {
// Clean up all subscriptions registered under grimPlugin
GrimAPIProvider.get().getEventBus().unregisterAllListeners(grimPlugin);
}
}
```
--------------------------------
### Track Player Lifecycle with GrimJoinEvent and GrimQuitEvent
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Tracks player join times and session duration using GrimJoinEvent and GrimQuitEvent. These events ensure the GrimUser object is ready before the handler is invoked.
```java
// Track join time per player
Map joinTimes = new ConcurrentHashMap<>();
bus.get(GrimJoinEvent.class).onJoin(grim, user -> {
joinTimes.put(user.getUniqueId(), System.currentTimeMillis());
getLogger().info("Grim now tracking: " + user.getName()
+ " (client=" + user.getVersionName()
+ ", brand=" + user.getBrand() + ")");
});
bus.get(GrimQuitEvent.class).onQuit(grim, user -> {
Long joined = joinTimes.remove(user.getUniqueId());
if (joined != null) {
long sessionMs = System.currentTimeMillis() - joined;
getLogger().info(user.getName() + " session: " + (sessionMs / 1000) + "s");
}
});
```
--------------------------------
### Manage User Alerts and Verbose Settings
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Manages alert preferences for a user, including enabling/disabling alerts, verbose output, and brand notifications. Some actions can be performed silently.
```java
AlertManager alerts = GrimAPIProvider.get().getAlertManager();
GrimUser user = GrimAPIProvider.get().getGrimUser(operatorUUID);
if (user != null) {
// Check current states
boolean hasAlerts = alerts.hasAlertsEnabled(user);
boolean hasVerbose = alerts.hasVerboseEnabled(user);
boolean hasBrands = alerts.hasBrandsEnabled(user);
// Enable alerts silently (no message to the player)
alerts.setAlertsEnabled(user, true, /*silent*/ true);
// Toggle verbose with a confirmation message
boolean newVerboseState = alerts.toggleVerbose(user); // notifies player
getLogger().info(user.getName() + " verbose is now: " + newVerboseState);
// Toggle brand notifications
alerts.toggleBrands(user); // notifies player
}
```
--------------------------------
### Handle Grim Configuration Reloads
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Listen for GrimReloadEvent to refresh local configuration caches after Grim reloads its settings. Ensure your plugin reacts to successful or failed reloads.
```java
bus.get(GrimReloadEvent.class).onReload(grim, success -> {
if (success) {
getLogger().info("Grim reloaded successfully — refreshing local config cache.");
refreshLocalConfig();
} else {
getLogger().severe("Grim reload failed! Local config may be stale.");
}
});
```
--------------------------------
### Access and Utilize GrimUser State
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Obtain a GrimUser object to access per-player metadata, timing data, check states, and thread-safe scheduling utilities. Use `runSafely` for Netty thread operations and `sendMessage` for Grim-formatted messages.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(player.getUniqueId());
if (user == null) {
getLogger().warning("Player is not tracked by Grim yet.");
return;
}
// Client metadata
getLogger().info("Name: " + user.getName());
getLogger().info("Version: " + user.getVersionName());
getLogger().info("Brand: " + user.getBrand());
getLogger().info("World: " + user.getWorldName());
getLogger().info("Tx ping: " + user.getTransactionPing() + "ms");
getLogger().info("KA ping: " + user.getKeepAlivePing() + "ms");
// Enumerate all active checks and their violation levels
for (AbstractCheck check : user.getChecks()) {
if (check.getViolations() > 0) {
getLogger().info(" " + check.getCheckName()
+ " VL=" + check.getViolations()
+ " decay=" + check.getDecay());
}
}
// Run code on the player's Netty thread safely
user.runSafely(() -> {
// Schedule an action after the next transaction is acknowledged
user.addRealTimeTaskNext(() ->
getLogger().info("Task ran post-transaction for " + user.getName()));
});
// Send a Grim-formatted message to the player
user.sendMessage("&cYou have been flagged for suspicious movement.");
```
--------------------------------
### Query Player Violation History with HistoryService
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Utilize the HistoryService to asynchronously retrieve a player's past sessions and violation details. Ensure the HistoryService is injected or obtained from the platform.
```java
// Assume historyService is injected or obtained from the platform
HistoryService history = /* ... */;
UUID playerUUID = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
// List the 10 most recent sessions
history.listSessions(playerUUID, /*cursor*/ null, /*pageSize*/ 10)
.thenAccept(page -> {
page.results().forEach(session ->
getLogger().info("Session #" + session.sessionOrdinal()
+ " started=" + session.startTime()
+ " flags=" + session.totalFlags()));
});
// Get full detail for a specific session
UUID sessionId = UUID.fromString("00000000-0000-0000-0000-000000000001");
history.getSessionDetail(playerUUID, sessionId)
.thenAccept(detail -> {
if (detail == null) {
getLogger().warning("Session not found.");
return;
}
detail.checkBuckets().forEach(bucket ->
getLogger().info(" " + bucket.checkName()
+ " flags=" + bucket.totalFlags()
+ " maxVL=" + bucket.maxVl()));
});
// Count total sessions for pagination
history.countSessions(playerUUID)
.thenAccept(count -> getLogger().info("Total sessions: " + count));
```
--------------------------------
### Track Player and Vehicle Setbacks
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Use GrimPlayerSetbackEvent and GrimVehicleSetbackEvent to observe Grim's setback packets. These events are observational and fire on the Netty thread. Track pending teleport IDs for correlating client confirmation packets.
```java
// Track pending setback teleport IDs for a sibling anticheat
Set pendingSetbacks = ConcurrentHashMap.newKeySet();
bus.get(GrimPlayerSetbackEvent.class)
.onPlayerSetback(grim, (user, teleportId, x, y, z, timestamp) -> {
pendingSetbacks.add(teleportId);
getLogger().fine(user.getName() + " setback tp#" + teleportId
+ " → (" + x + ", " + y + ", " + z + ")");
});
bus.get(GrimVehicleSetbackEvent.class)
.onVehicleSetback(grim, (user, x, y, z, timestamp) -> {
getLogger().fine(user.getName() + " vehicle setback → ("
+ x + ", " + y + ", " + z + ")");
});
```
--------------------------------
### Deprecated Event Subscription
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Use the deprecated overload for subscribing to events if you don't want to hold a GrimPlugin reference. It resolves the context through the bus's plugin resolver. It is recommended to use the non-deprecated overload instead.
```java
// Works but warns. Call api.getGrimPlugin(this) once and use the
// non-deprecated overload instead.
bus.get(FlagEvent.class).onFlag((Object) this, (u, c, v, cancelled) -> cancelled);
```
--------------------------------
### Manage Per-Player Feature Flags
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Handles persistent, reload-safe feature toggles for individual players. Allows checking, setting, and listing feature states.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(uuid);
if (user != null) {
FeatureManager fm = user.getFeatureManager();
// Check if a custom feature is enabled
boolean replayEnabled = fm.isFeatureEnabled("replay-recording");
// Get detailed state (ENABLED / DISABLED / null if not registered)
FeatureState state = fm.getFeatureState("replay-recording");
if (state == null) {
getLogger().warning("Feature 'replay-recording' is not registered.");
}
// Set feature state
fm.setFeatureState("replay-recording", FeatureState.ENABLED);
// List all registered feature keys for this player
for (String key : fm.getFeatureKeys()) {
getLogger().info("Feature: " + key + " = " + fm.getFeatureState(key));
}
}
```
--------------------------------
### GrimReloadEvent
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Fired asynchronously after Grim reloads its configuration. Use this to re-read any config values your plugin mirrors from Grim.
```APIDOC
## `GrimReloadEvent` — Config reload notification
Fired asynchronously after Grim reloads its configuration. Use this to re-read any config values your plugin mirrors from Grim.
```java
bus.get(GrimReloadEvent.class).onReload(grim, success -> {
if (success) {
getLogger().info("Grim reloaded successfully — refreshing local config cache.");
refreshLocalConfig();
} else {
getLogger().severe("Grim reload failed! Local config may be stale.");
}
});
```
```
--------------------------------
### HistoryService - Querying Player Violation History
Source: https://context7.com/grimanticheat/grimapi/llms.txt
A high-level async facade for listing a player's past sessions and drilling into per-session violation detail. Obtain it from the platform's storage wiring when available.
```APIDOC
## `HistoryService` — Querying player violation history (Experimental)
High-level async facade for listing a player's past sessions and drilling into per-session violation detail. Obtain it from the platform's storage wiring when available.
```java
// Assume historyService is injected or obtained from the platform
HistoryService history = /* ... */;
UUID playerUUID = UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5");
// List the 10 most recent sessions
history.listSessions(playerUUID, /*cursor*/ null, /*pageSize*/ 10)
.thenAccept(page -> {
page.results().forEach(session ->
getLogger().info("Session #" + session.sessionOrdinal()
+ " started=" + session.startTime()
+ " flags=" + session.totalFlags()));
});
// Get full detail for a specific session
UUID sessionId = UUID.fromString("00000000-0000-0000-0000-000000000001");
history.getSessionDetail(playerUUID, sessionId)
.thenAccept(detail -> {
if (detail == null) {
getLogger().warning("Session not found.");
return;
}
detail.checkBuckets().forEach(bucket ->
getLogger().info(" " + bucket.checkName()
+ " flags=" + bucket.totalFlags()
+ " maxVL=" + bucket.maxVl()));
});
// Count total sessions for pagination
history.countSessions(playerUUID)
.thenAccept(count -> getLogger().info("Total sessions: " + count));
```
```
--------------------------------
### GrimPlayerSetbackEvent / GrimVehicleSetbackEvent
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Fired on the Netty thread when Grim sends a teleport-setback (on-foot) or vehicle-setback packet. Observational only — not cancellable. Carry the outbound teleport id so you can correlate the client's confirm packet.
```APIDOC
## `GrimPlayerSetbackEvent` / `GrimVehicleSetbackEvent` — Setback tracking
Fired on the Netty thread when Grim sends a teleport-setback (on-foot) or vehicle-setback packet. Observational only — not cancellable. Carry the outbound teleport id so you can correlate the client's confirm packet.
```java
// Track pending setback teleport IDs for a sibling anticheat
Set pendingSetbacks = ConcurrentHashMap.newKeySet();
bus.get(GrimPlayerSetbackEvent.class)
.onPlayerSetback(grim, (user, teleportId, x, y, z, timestamp) -> {
pendingSetbacks.add(teleportId);
getLogger().fine(user.getName() + " setback tp#" + teleportId
+ " → (" + x + ", " + y + ", " + z + ")");
});
bus.get(GrimVehicleSetbackEvent.class)
.onVehicleSetback(grim, (user, x, y, z, timestamp) -> {
getLogger().fine(user.getName() + " vehicle setback → ("
+ x + ", " + y + ", " + z + ")");
});
```
```
--------------------------------
### Cache Event Channels for Hot Paths
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Cache the event channel in a static final field to avoid repeated lookups on the hot path. This allows the JIT compiler to fold the lookup away entirely, improving performance by eliminating map lookups and event-object allocation.
```java
public final class MyHotPathClass {
private static final FlagEvent.Channel FLAG =
GrimAPIProvider.get().getEventBus().get(FlagEvent.class);
public void process() {
// No map lookup, no event-object allocation.
FLAG.fire(user, check, verbose);
}
}
```
--------------------------------
### Monitor All Outbound Teleport Packets
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Subscribe to GrimTeleportEvent to track every teleport packet Grim sends. This is useful for packet-tracking plugins that need to maintain a deque of pending teleports. Fires on the Netty thread.
```java
bus.get(GrimTeleportEvent.class).onTeleport(grim, (user, teleportId, timestamp) -> {
getLogger().fine("Teleport packet sent to " + user.getName()
+ " id=" + teleportId
+ " at=" + timestamp);
});
```
--------------------------------
### FeatureManager - Per-player feature flags
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Manages persistent, reload-safe per-player feature toggles. Retrieved via `GrimUser#getFeatureManager()`, these features survive server reloads without re-registration.
```APIDOC
## FeatureManager - Per-player feature flags
Persistent, reload-safe per-player feature toggles. Retrieve via `GrimUser#getFeatureManager()`. Features survive `/grim reload` without re-registration.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(uuid);
if (user != null) {
FeatureManager fm = user.getFeatureManager();
// Check if a custom feature is enabled
boolean replayEnabled = fm.isFeatureEnabled("replay-recording");
// Get detailed state (ENABLED / DISABLED / null if not registered)
FeatureState state = fm.getFeatureState("replay-recording");
if (state == null) {
getLogger().warning("Feature 'replay-recording' is not registered.");
}
// Set feature state
fm.setFeatureState("replay-recording", FeatureState.ENABLED);
// List all registered feature keys for this player
for (String key : fm.getFeatureKeys()) {
getLogger().info("Feature: " + key + " = " + fm.getFeatureState(key));
}
}
```
```
--------------------------------
### Find Top Check for Player
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Retrieves all checks for a player and finds the one with the highest violation count. Logs details of the top check if found.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(uuid);
if (user != null) {
user.getChecks().stream()
.filter(c -> c.getViolations() > 0)
.max(Comparator.comparingDouble(AbstractCheck::getViolations))
.ifPresent(check -> {
getLogger().info("Top check: " + check.getCheckName()
+ " key=" + check.getStableKey()
+ " VL=" + check.getViolations()
+ " setbackAt=" + check.getSetbackVL()
+ " experimental=" + check.isExperimental());
});
}
```
--------------------------------
### Handle FlagEvent with Custom Logic
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Logs flag events to a custom audit log and suppresses experimental checks. Returns `false` to un-cancel and `true` to cancel the flag.
```java
bus.get(FlagEvent.class).onFlag(grim, (user, check, verbose, cancelled) -> {
String checkName = check.getCheckName();
double vl = check.getViolations();
long lastVl = check.getLastViolationTime();
// Log every flag to a custom audit log
auditLog.write(String.format("[%tT] %s | %s | VL=%.1f | %s%n",
lastVl, user.getName(), checkName, vl, verbose));
// Suppress experimental checks to prevent false positives in production
if (check.isExperimental()) {
getLogger().fine("Suppressed experimental flag: " + checkName);
return false; // un-cancel
}
return cancelled; // preserve state set by lower-priority handlers
});
```
--------------------------------
### Unregistering All Listeners
Source: https://github.com/grimanticheat/grimapi/blob/master/README.md
Explains how to unregister all event listeners associated with a specific plugin.
```APIDOC
## Unregistering All Listeners
### Description
Clean up all event handlers registered by a specific `GrimPlugin` or context.
### Method
`EventBus.unregisterAllListeners(Object pluginOrContext)`
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Request Example
```java
@Override
public void onDisable() {
EventBus bus = GrimAPIProvider.get().getEventBus();
bus.unregisterAllListeners(this); // or: bus.unregisterAllListeners(grim)
}
```
### Response
#### Success Response (200)
None
#### Response Example
None
```
--------------------------------
### GrimUser
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Represents a per-player state tracked by Grim. Obtain via `api.getGrimUser(uuid)`. Exposes client metadata, timing data, check state, and thread-safe scheduling utilities.
```APIDOC
## `GrimUser` — Per-player state
Represents a player currently tracked by Grim. Obtain via `api.getGrimUser(uuid)`. Exposes client metadata, timing data, check state, and thread-safe scheduling utilities.
```java
GrimUser user = GrimAPIProvider.get().getGrimUser(player.getUniqueId());
if (user == null) {
getLogger().warning("Player is not tracked by Grim yet.");
return;
}
// Client metadata
getLogger().info("Name: " + user.getName());
getLogger().info("Version: " + user.getVersionName());
getLogger().info("Brand: " + user.getBrand());
getLogger().info("World: " + user.getWorldName());
getLogger().info("Tx ping: " + user.getTransactionPing() + "ms");
getLogger().info("KA ping: " + user.getKeepAlivePing() + "ms");
// Enumerate all active checks and their violation levels
for (AbstractCheck check : user.getChecks()) {
if (check.getViolations() > 0) {
getLogger().info(" " + check.getCheckName()
+ " VL=" + check.getViolations()
+ " decay=" + check.getDecay());
}
}
// Run code on the player's Netty thread safely
user.runSafely(() -> {
// Schedule an action after the next transaction is acknowledged
user.addRealTimeTaskNext(() ->
getLogger().info("Task ran post-transaction for " + user.getName()));
});
// Send a Grim-formatted message to the player
user.sendMessage("&cYou have been flagged for suspicious movement.");
```
```
--------------------------------
### Hot-Path Channel Caching for FlagEvent
Source: https://context7.com/grimanticheat/grimapi/llms.txt
Stores the EventChannel in a static final field to eliminate ConcurrentHashMap lookups on every fire/subscribe call, enabling JIT inlining for direct dispatch.
```java
public class FlagMonitor extends JavaPlugin {
// Resolved once at class-load time — zero map lookup on every flag
private static final FlagEvent.Channel FLAG_CHANNEL =
GrimAPIProvider.get().getEventBus().get(FlagEvent.class);
private GrimPlugin grimPlugin;
@Override
public void onEnable() {
grimPlugin = GrimAPIProvider.get().getGrimPlugin(this);
FLAG_CHANNEL.onFlag(grimPlugin, (user, check, verbose, cancelled) -> {
// ultra-low-overhead handler
if (check.getViolations() >= check.getSetbackVL()) {
getLogger().warning("Setback threshold reached for " + user.getName());
}
return cancelled;
});
}
}
```