Compatibility with LightningGrim
This commit is contained in:
1 parent
ce0def7f8d
commit
9c3be631ff
4 files changed
+262
-1
No files matched your search
@@ -0,0 +1,255 @@
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// Authors: NotAlexNoyle.
|
||||
package net.trueog.flyingbowog;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
// Soft integration with LightningGrim (Grim anticheat), the Grim-side twin of NCPHook.
|
||||
// While a player rides a Flying Bow arrow -- and during the momentum coast after the
|
||||
// arrow is gone -- they move faster than survival physics allows. Grim's prediction
|
||||
// engine would treat that as illegal movement, so we exempt the player for the duration.
|
||||
//
|
||||
// Grim has no per-group exempt API, and its public API (AbstractCheck#setEnabled) only
|
||||
// gates packet modification, not flagging/setback (Check#flag short-circuits on
|
||||
// player.disableGrim, not isEnabled). The one lever that fully exempts -- skipping
|
||||
// prediction, flags and setbacks alike -- is the GrimPlayer#disableGrim field; Grim
|
||||
// declares it once and never reassigns it, so it is a clean external toggle.
|
||||
//
|
||||
// We reach it without a compile-time dependency: Grim publishes only its api package
|
||||
// (no GrimPlayer impl) and disableGrim is an impl field, so reflection is required
|
||||
// regardless. The GrimAbstractAPI instance is fetched name-agnostically from Bukkit's
|
||||
// ServicesManager (Grim registers it there); getGrimUser(UUID) returns the GrimPlayer,
|
||||
// whose public disableGrim field we flip on its netty thread via GrimUser#runSafely.
|
||||
// If Grim is absent the hook is a silent no-op; if it is present but the API will not
|
||||
// bind we log once, because that is the only way a ride can still be flagged.
|
||||
public final class GrimHook {
|
||||
|
||||
// Cached reflection handles, resolved once on first use. ready == true means
|
||||
// the
|
||||
// API bound and exemption calls will be attempted.
|
||||
private static boolean resolved = false;
|
||||
private static boolean ready = false;
|
||||
private static Class<?> apiClass;
|
||||
private static Method getGrimUserMethod;
|
||||
private static Method runSafelyMethod;
|
||||
// Resolved lazily from the first GrimUser instance and cached (impl class
|
||||
// field).
|
||||
private static Field disableGrimField;
|
||||
|
||||
// One-shot guards so a persistent runtime fault logs once, not once per shot.
|
||||
private static boolean warnedNoUser = false;
|
||||
private static boolean warnedInvokeFailed = false;
|
||||
|
||||
// Pending "unexempt once grounded" pollers, keyed by player. A new ride cancels
|
||||
// the
|
||||
// previous poller so a late landing can never unexempt mid-flight.
|
||||
private static final Map<UUID, BukkitTask> pendingUnexempt = new ConcurrentHashMap<>();
|
||||
|
||||
// Cap on how long the exemption is held after dismount while waiting for the
|
||||
// player
|
||||
// to land (server ticks); bounds the worst case so it can never leak
|
||||
// permanently.
|
||||
private static final int MAX_GROUNDING_TICKS = 200;
|
||||
|
||||
private GrimHook() {
|
||||
|
||||
}
|
||||
|
||||
// Exempt the player from all Grim checks while riding a Flying Bow arrow.
|
||||
public static void exempt(Player player) {
|
||||
|
||||
if (!resolve())
|
||||
return;
|
||||
|
||||
// A new ride supersedes any pending post-ride grounding poll.
|
||||
cancelPendingUnexempt(player.getUniqueId());
|
||||
|
||||
setDisableGrim(player, true);
|
||||
|
||||
}
|
||||
|
||||
// Called when the ride ends (dismount). The player keeps the arrow's momentum
|
||||
// and
|
||||
// coasts through the air, which Grim still checks as a normal player move, so
|
||||
// we
|
||||
// hold the exemption until the player has landed (bounded by
|
||||
// MAX_GROUNDING_TICKS).
|
||||
public static void unexemptWhenGrounded(Player player) {
|
||||
|
||||
if (!resolve())
|
||||
return;
|
||||
|
||||
UUID id = player.getUniqueId();
|
||||
cancelPendingUnexempt(id);
|
||||
|
||||
Plugin plugin = FlyingBowOG.getPlugin();
|
||||
// Already grounded, or no scheduler available: restore immediately.
|
||||
if (plugin == null || player.isOnGround()) {
|
||||
|
||||
setDisableGrim(player, false);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final int[] ticks = { 0 };
|
||||
BukkitTask task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
|
||||
Player online = Bukkit.getPlayer(id);
|
||||
ticks[0]++;
|
||||
|
||||
if (online == null || !online.isOnline() || online.isOnGround() || ticks[0] >= MAX_GROUNDING_TICKS) {
|
||||
|
||||
cancelPendingUnexempt(id);
|
||||
if (online != null)
|
||||
setDisableGrim(online, false);
|
||||
|
||||
}
|
||||
|
||||
}, 1L, 1L);
|
||||
|
||||
pendingUnexempt.put(id, task);
|
||||
|
||||
}
|
||||
|
||||
// Flip GrimPlayer#disableGrim for the player on Grim's netty thread
|
||||
// (runSafely).
|
||||
private static void setDisableGrim(Player player, boolean value) {
|
||||
|
||||
if (!resolve())
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
Object api = Bukkit.getServicesManager().load(apiClass);
|
||||
// Service not registered yet (Grim still starting): nothing to toggle.
|
||||
if (api == null)
|
||||
return;
|
||||
|
||||
Object user = getGrimUserMethod.invoke(api, player.getUniqueId());
|
||||
if (user == null) {
|
||||
|
||||
if (!warnedNoUser) {
|
||||
|
||||
warnedNoUser = true;
|
||||
log(Level.WARNING, "LightningGrim has no tracked user for " + player.getName()
|
||||
+ "; the Flying Bow ride may be flagged as illegal movement.");
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
final Object grimUser = user;
|
||||
// disableGrim is an impl field; resolve from the live object once and cache.
|
||||
if (disableGrimField == null)
|
||||
disableGrimField = grimUser.getClass().getField("disableGrim");
|
||||
|
||||
// Run on the player's netty thread so the write is ordered with Grim's reads.
|
||||
runSafelyMethod.invoke(grimUser, (Runnable) () -> {
|
||||
|
||||
try {
|
||||
|
||||
disableGrimField.setBoolean(grimUser, value);
|
||||
|
||||
} catch (Throwable t) {
|
||||
|
||||
if (!warnedInvokeFailed) {
|
||||
|
||||
warnedInvokeFailed = true;
|
||||
log(Level.WARNING,
|
||||
"Failed to set LightningGrim disableGrim for " + player.getName() + ": " + t);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (Throwable t) {
|
||||
|
||||
if (!warnedInvokeFailed) {
|
||||
|
||||
warnedInvokeFailed = true;
|
||||
log(Level.WARNING, "Failed to toggle LightningGrim exemption for " + player.getName() + ": " + t);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Cancel and forget any pending grounding poller for a player.
|
||||
private static void cancelPendingUnexempt(UUID id) {
|
||||
|
||||
BukkitTask task = pendingUnexempt.remove(id);
|
||||
if (task != null)
|
||||
task.cancel();
|
||||
|
||||
}
|
||||
|
||||
// Bind the Grim API on first use. Returns whether the hook is usable.
|
||||
private static synchronized boolean resolve() {
|
||||
|
||||
if (resolved)
|
||||
return ready;
|
||||
resolved = true;
|
||||
|
||||
// Grim not installed: stay silent, the hook is simply inactive.
|
||||
if (Bukkit.getPluginManager().getPlugin("LightningGrim") == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
|
||||
apiClass = Class.forName("ac.grim.grimac.api.GrimAbstractAPI");
|
||||
Class<?> userClass = Class.forName("ac.grim.grimac.api.GrimUser");
|
||||
|
||||
// getGrimUser(UUID) is the non-deprecated lookup; runSafely runs our write
|
||||
// on the player's netty thread.
|
||||
getGrimUserMethod = apiClass.getMethod("getGrimUser", UUID.class);
|
||||
runSafelyMethod = userClass.getMethod("runSafely", Runnable.class);
|
||||
|
||||
ready = true;
|
||||
log(Level.INFO, "Hooked into LightningGrim; Flying Bow rides are exempted from Grim checks.");
|
||||
|
||||
} catch (Throwable t) {
|
||||
|
||||
// Grim present but its API would not bind -- this is the failure mode that
|
||||
// makes exemptions silently no-op, so it must be visible.
|
||||
ready = false;
|
||||
log(Level.WARNING, "LightningGrim is installed but its API could not be bound (" + t
|
||||
+ "); Flying Bow rides will be flagged. Check that the running LightningGrim version exposes"
|
||||
+ " ac.grim.grimac.api.GrimAbstractAPI via the Bukkit services manager.");
|
||||
|
||||
}
|
||||
|
||||
return ready;
|
||||
|
||||
}
|
||||
|
||||
// Route log lines through the plugin logger, falling back to the server logger.
|
||||
private static void log(Level level, String message) {
|
||||
|
||||
Plugin plugin = FlyingBowOG.getPlugin();
|
||||
if (plugin != null) {
|
||||
|
||||
plugin.getLogger().log(level, message);
|
||||
|
||||
} else {
|
||||
|
||||
Bukkit.getLogger().log(level, "[FlyingBow-OG] " + message);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Authors: NotAlexNoyle.
|
||||
package net.trueog.flyingbowog.Listeners;
|
||||
|
||||
import net.trueog.flyingbowog.GrimHook;
|
||||
import net.trueog.flyingbowog.NCPHook;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.entity.Arrow;
|
||||
@@ -39,6 +40,7 @@ public class OnEntityDismountEvent implements Listener {
|
||||
// landed -- the post-ride coast is checked as a normal player move and would
|
||||
// otherwise raise MOVING_CREATIVEFLY.
|
||||
NCPHook.unexemptMovingWhenGrounded(player);
|
||||
GrimHook.unexemptWhenGrounded(player);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package net.trueog.flyingbowog.Listeners;
|
||||
import com.destroystokyo.paper.event.player.PlayerArmorChangeEvent; // Purpur 1.19
|
||||
import java.util.Map;
|
||||
import net.trueog.flyingbowog.FlyingBowOG;
|
||||
import net.trueog.flyingbowog.GrimHook;
|
||||
import net.trueog.flyingbowog.NCPHook;
|
||||
import net.trueog.utilitiesog.UtilitiesOG;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -82,6 +83,9 @@ public class OnEntityShootBowEvent implements Listener {
|
||||
// Cleared on dismount in OnEntityDismountEvent.
|
||||
NCPHook.exemptMoving(shooter);
|
||||
|
||||
// Same for LightningGrim (Grim anticheat); cleared on dismount.
|
||||
GrimHook.exempt(shooter);
|
||||
|
||||
}, 1L);
|
||||
|
||||
// Strip Infinity from the bow (too OP for this item).
|
||||
|
||||
@@ -4,7 +4,7 @@ main: net.trueog.flyingbowog.FlyingBowOG
|
||||
authors: [barny1094875, NotAlexNoyle]
|
||||
api-version: 1.19
|
||||
depend: [Item-NBT-API]
|
||||
softdepend: [NoCheatPlus]
|
||||
softdepend: [NoCheatPlus, LightningGrim]
|
||||
commands:
|
||||
flyingbow:
|
||||
usage: /flyingbow
|
||||
|
||||
Reference in new issue
Block a user