### 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 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 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; }); } } ```