Compare commits

...
Author SHA1 Message Date
Axionize 4933fd9d4f feat: add breakpoint-tolerant clock system and refactor GrimPlayer time handling
This commit Implement zero-cost breakpoint-tolerant time tracking.

It introduces a new time architecture for GrimPlayer that allows developers to pause execution at breakpoints without triggering Timer, FastBreak, or Velocity violations upon resumption.

Changes:
- Refactored `GrimPlayer` to use `nano()`/`now()` overrides instead of direct System calls.
- Added `DebugGrimPlayer` subclass which uses `ThreadLocal` to track intra-packet execution time.
- Logic automatically absorbs execution pauses >200ms (breakpoints) while correctly preserving inter-packet delays (server lag/AFK).
- Added `processingPacket()` hooks to the Packet Dispatcher and Tick Manager to manage thread-local timers.
- Performance impact is 0 in production due to JIT monomorphic inlining and dead code elimination of the base class methods.
2025-11-27 20:23:11 -05:00
22 changed files with 222 additions and 48 deletions

No files matched your search

@@ -96,7 +96,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
if (event.isCancelled()) return false;
player.punishmentManager.handleViolation(this);
lastViolationTime = System.currentTimeMillis();
lastViolationTime = player.now();
violations++;
return true;
}
@@ -0,0 +1,82 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.protocol.player.User;
import org.jetbrains.annotations.NotNull;
public class DebugGrimPlayer extends GrimPlayer {
private static final long THRESHOLD_NANO = 200_000_000L; // 200ms
private static final long THRESHOLD_MILLIS = 200;
// Global Offsets: Volatile so all threads see the pause adjustment immediately.
// When the Netty thread pauses for 60s, it updates this.
// The Scheduler thread sees the update and "stops" the timeout check.
private volatile long offsetNano = 0;
private volatile long offsetMillis = 0;
// ThreadLocal: Each thread tracks its OWN execution time.
// This prevents the Scheduler Thread (pollData) from resetting the timer
// while the Netty Thread is sitting at a breakpoint.
private final ThreadLocal<Long> packetStartNano = ThreadLocal.withInitial(System::nanoTime);
private final ThreadLocal<Long> packetStartMillis = ThreadLocal.withInitial(System::currentTimeMillis);
public DebugGrimPlayer(@NotNull User user) {
super(user, System.currentTimeMillis(), System.nanoTime());
}
@Override
public void processingPacket() {
// Reset the timer ONLY for the current thread (Netty or Scheduler)
this.packetStartNano.set(System.nanoTime());
this.packetStartMillis.set(System.currentTimeMillis());
}
@Override
public long nano() {
// SAFETY CHECK:
// If something in the parent constructor calls nano() BEFORE we are fully initialized,
// packetStartNano will be null. We must fallback to System time to prevent NPE.
if (packetStartNano == null) return System.nanoTime();
long sysTime = System.nanoTime();
// 1. Get start time for THIS thread
long start = packetStartNano.get();
long executionTime = sysTime - start;
// 2. Check if THIS thread was paused/stuck
if (executionTime > THRESHOLD_NANO) {
// Update the global offset so ALL threads know to skip this time
synchronized (this) {
offsetNano += executionTime;
}
// Reset this thread's timer so we don't loop
packetStartNano.set(sysTime);
} else if (executionTime < 0) {
// Self-healing for rare thread-local init edge cases or system clock jumps
packetStartNano.set(sysTime);
}
// 3. Return System Time minus the Global Pause Offset
return sysTime - offsetNano;
}
@Override
public long now() {
if (packetStartMillis == null) return System.currentTimeMillis();
long sysTime = System.currentTimeMillis();
long start = packetStartMillis.get();
long executionTime = sysTime - start;
if (executionTime > THRESHOLD_MILLIS) {
synchronized (this) {
offsetMillis += executionTime;
}
packetStartMillis.set(sysTime);
}
return sysTime - offsetMillis;
}
}
@@ -0,0 +1,35 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import java.lang.management.ManagementFactory;
public class GrimDebugSettings {
public static boolean shouldEnable() {
// 1. If the argument is disabled, never enable debug mode.
if (!CommonGrimArguments.DEBUG_BREAKPOINT_MODE.value()) {
return false;
}
// 2. Check for Debugger (Startup Agent OR Dynamic Attach)
return isDebuggerAttached();
}
private static boolean isDebuggerAttached() {
// Fast Path: Check startup arguments for -agentlib:jdwp
if (ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("jdwp")) {
return true;
}
// Slow Path (Dynamic Attach): Scan threads for JDWP/Signal Dispatcher
try {
for (Thread t : Thread.getAllStackTraces().keySet()) {
String name = t.getName();
if (name.contains("JDWP")) return true;
}
} catch (Throwable ignored) {}
return false;
}
}
@@ -23,7 +23,7 @@ public class BadPacketsR extends Check implements PacketCheck {
public void onPacketReceive(final PacketReceiveEvent event) {
if (isTransaction(event.getPacketType()) && player.packetStateData.lastTransactionPacketWasValid) {
long ms = (player.getPlayerClockAtLeast() - clock) / 1000000L;
long diff = (System.currentTimeMillis() - lastTransTime);
long diff = (player.now() - lastTransTime);
if (diff > 2000 && ms > 2000) {
if (positions == 0 && clock != 0 && player.gamemode != GameMode.SPECTATOR && !player.compensatedEntities.self.isDead) {
flag("time=" + ms + "ms, " + "lst=" + diff + "ms, positions=" + positions);
@@ -34,7 +34,7 @@ public class BadPacketsR extends Check implements PacketCheck {
player.compensatedWorld.removeInvalidPistonLikeStuff(oldTransId);
positions = 0;
clock = player.getPlayerClockAtLeast();
lastTransTime = System.currentTimeMillis();
lastTransTime = player.now();
oldTransId = player.lastTransactionSent.get();
}
}
@@ -67,12 +67,12 @@ public class FastBreak extends Check implements BlockBreakCheck {
// * can we translate back "up" to server version and run check against server version to avoid loading older registries?
WrappedBlockState block = clientOlderThanServer ? WrappedBlockState.getByGlobalId(player.getClientVersion(), player.getViaTranslatedClientBlockID(blockBreak.block.getGlobalId())) : blockBreak.block;
startBreak = System.currentTimeMillis() - (targetBlockPosition == null ? 50 : 0); // ???
startBreak = player.now() - (targetBlockPosition == null ? 50 : 0); // ???
targetBlockPosition = blockBreak.position;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, block);
double breakDelay = System.currentTimeMillis() - lastFinishBreak;
double breakDelay = player.now() - lastFinishBreak;
if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
@@ -91,7 +91,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockPosition != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double realTime = player.now() - startBreak;
double diff = predictedTime - realTime;
clampBalance();
@@ -109,7 +109,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
}
// also set start time because the breaking netcode is fucked on 1.14.4+
lastFinishBreak = startBreak = System.currentTimeMillis();
lastFinishBreak = startBreak = player.now();
}
}
@@ -12,18 +12,18 @@ public class NegativeTimer extends Timer implements PostPredictionCheck {
public NegativeTimer(GrimPlayer player) {
super(player);
timerBalanceRealTime = System.nanoTime() + clockDrift;
timerBalanceRealTime = player.nano() + clockDrift;
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// We can't negative timer check a 1.9+ player who is standing still.
if (player.uncertaintyHandler.lastPointThree.hasOccurredSince(2) || !predictionComplete.isChecked()) {
timerBalanceRealTime = System.nanoTime() + clockDrift;
timerBalanceRealTime = player.nano() + clockDrift;
}
if (timerBalanceRealTime < lastMovementPlayerClock - clockDrift) {
int lostMS = (int) ((System.nanoTime() - timerBalanceRealTime) / 1e6);
int lostMS = (int) ((player.nano() - timerBalanceRealTime) / 1e6);
flagAndAlertWithSetback("-" + lostMS);
timerBalanceRealTime += 50e6;
}
@@ -14,8 +14,8 @@ public class Timer extends Check implements PacketCheck {
protected long timerBalanceRealTime = 0;
// Default value is real time minus max keep-alive time
protected long knownPlayerClockTime = (long) (System.nanoTime() - 6e10);
protected long lastMovementPlayerClock = (long) (System.nanoTime() - 6e10);
protected long knownPlayerClockTime = (long) (player.nano() - 6e10);
protected long lastMovementPlayerClock = (long) (player.nano() - 6e10);
// How long should the player be able to fall back behind their ping? (nanos)
// Default: 120 milliseconds
@@ -71,7 +71,7 @@ public class Timer extends Check implements PacketCheck {
}
public void doCheck(final PacketReceiveEvent event) {
if (timerBalanceRealTime > System.nanoTime()) {
if (timerBalanceRealTime > player.nano()) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
@@ -19,7 +19,7 @@ public class TimerLimit extends Timer {
@Override
public void doCheck(final PacketReceiveEvent event) {
// 1:1 with Timer minus cancelling the packet
if (timerBalanceRealTime > System.nanoTime()) {
if (timerBalanceRealTime > player.nano()) {
// If timer check already flagged, don't flag.
if (!event.isCancelled()) {
if (flagAndAlert() && shouldSetback()) {
@@ -38,8 +38,8 @@ public class TimerLimit extends Timer {
protected void limitFallBehind() {
// Limit using transaction ping if over 1000ms (default)
long playerClock = lastMovementPlayerClock;
if (limitAbuseOverPing != -1 && System.nanoTime() - playerClock > limitAbuseOverPing) {
playerClock = System.nanoTime() - limitAbuseOverPing;
if (limitAbuseOverPing != -1 && player.nano() - playerClock > limitAbuseOverPing) {
playerClock = player.nano() - limitAbuseOverPing;
}
timerBalanceRealTime = Math.max(timerBalanceRealTime, playerClock - clockDrift);
}
@@ -495,7 +495,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT) {
WrapperPlayClientPlayerBlockPlacement packet = new WrapperPlayClientPlayerBlockPlacement(event);
player.lastBlockPlaceUseItem = System.currentTimeMillis();
player.lastBlockPlaceUseItem = player.now();
ItemStack placedWith = player.inventory.getHeldItem();
if (packet.getHand() == InteractionHand.OFF_HAND) {
@@ -562,7 +562,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
WrapperPlayClientUseItem packet = new WrapperPlayClientUseItem(event);
player.placeUseItemPackets.add(new BlockPlaceSnapshot(packet, player.isSneaking));
player.lastBlockPlaceUseItem = System.currentTimeMillis();
player.lastBlockPlaceUseItem = player.now();
}
// Call the packet checks last as they can modify the contents of the packet
@@ -663,7 +663,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void handleFlying(GrimPlayer player, double x, double y, double z, float yaw, float pitch, boolean hasPosition, boolean hasLook, boolean onGround, TeleportAcceptData teleportData, PacketReceiveEvent event) {
long now = System.currentTimeMillis();
long now = player.now();
if (!hasPosition) {
// This may need to be secured later, although nothing that is very important relies on this
@@ -759,7 +759,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void handleDigging(GrimPlayer player, PacketReceiveEvent event) {
player.lastBlockBreak = System.currentTimeMillis();
player.lastBlockBreak = player.now();
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
final DiggingAction action = packet.getAction();
@@ -72,7 +72,7 @@ public class PacketPingListener extends PacketListenerAbstract {
if (id <= 0) {
if (player.didWeSendThatTrans.remove(id)) {
player.packetStateData.lastServerTransWasValid = true;
player.transactionsSent.add(new Pair<>(id, System.nanoTime()));
player.transactionsSent.add(new Pair<>(id, player.nano()));
player.lastTransactionSent.getAndIncrement();
}
}
@@ -91,7 +91,7 @@ public class PacketPingListener extends PacketListenerAbstract {
Short shortID = ((short) id);
if (player.didWeSendThatTrans.remove(shortID)) {
player.packetStateData.lastServerTransWasValid = true;
player.transactionsSent.add(new Pair<>(shortID, System.nanoTime()));
player.transactionsSent.add(new Pair<>(shortID, player.nano()));
player.lastTransactionSent.getAndIncrement();
}
}
@@ -228,7 +228,7 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
// do we need to do this with block breaks too?
// Prevent issues if the player switches slots, while lagging, standing still, and is placing blocks
CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, player.now());
if (player.packetStateData.lastSlotSelected != slot) {
if (player.isResetItemUsageOnSlotChange() && GrimAPI.INSTANCE.getItemResetHandler().getItemUsageHand(player.platformPlayer) == InteractionHand.MAIN_HAND) {
@@ -212,7 +212,7 @@ public class PacketPlayerRespawn extends PacketListenerAbstract {
player.clientVelocity = new Vector3dm();
if (!GrimAPI.INSTANCE.getSpectateManager().isSpectating(player.uuid)) {
player.gamemode = respawn.getGameMode();
}
}
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) {
player.compensatedWorld.setDimension(respawn.getDimensionType(), event.getUser());
}
@@ -31,7 +31,7 @@ public class PacketWorldBorder extends Check implements PacketCheck {
}
public double getCurrentDiameter() {
double d0 = (double) (System.currentTimeMillis() - startTime) / ((double) endTime - startTime);
double d0 = (double) (player.now() - startTime) / ((double) endTime - startTime);
return d0 < 1.0D ? GrimMath.lerp(d0, oldDiameter, newDiameter) : newDiameter;
}
@@ -125,7 +125,7 @@ public class PacketWorldBorder extends Check implements PacketCheck {
private void setLerp(double oldDiameter, double newDiameter, long length) {
this.oldDiameter = oldDiameter;
this.newDiameter = newDiameter;
this.startTime = System.currentTimeMillis();
this.startTime = player.now();
this.endTime = this.startTime + length;
}
}
@@ -158,7 +158,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
Vector3i blockPosition = blockChange.getBlockPosition();
// Don't spam transactions (block changes are sent in batches)
if (Math.abs(blockPosition.getX() - player.x) < range && Math.abs(blockPosition.getY() - player.y) < range && Math.abs(blockPosition.getZ() - player.z) < range &&
player.lastTransSent + 2 < System.currentTimeMillis())
player.lastTransSent + 2 < player.now())
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockChange.getBlockId()));
@@ -172,7 +172,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
final var blocks = multiBlockChange.getBlocks();
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
// Don't send a transaction unless it's within 16 blocks of the player
if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < System.currentTimeMillis()) {
if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < player.now()) {
player.sendTransaction();
break;
}
@@ -24,7 +24,7 @@ public class ActionManager extends Check implements PacketCheck {
if (action.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
player.totalFlyingPacketsSent = 0;
attacking = true;
lastAttack = System.currentTimeMillis();
lastAttack = player.now();
}
} else if (isTickPacketIncludingNonMovement(event.getPacketType())) {
player.totalFlyingPacketsSent++;
@@ -33,6 +33,6 @@ public class ActionManager extends Check implements PacketCheck {
}
public boolean hasAttackedSince(long time) {
return System.currentTimeMillis() - lastAttack < time;
return player.now() - lastAttack < time;
}
}
@@ -75,8 +75,10 @@ public class CheckManager {
private final ClassToInstanceMap<BlockPlaceCheck> blockPlaceChecks;
private final ClassToInstanceMap<PostPredictionCheck> postPredictionChecks;
private PacketEntityReplication packetEntityReplication = null;
private final GrimPlayer player;
public CheckManager(GrimPlayer player) {
this.player = player;
packetChecks = new ImmutableClassToInstanceMap.Builder<PacketCheck>()
.put(PacketOrderProcessor.class, player.packetOrderProcessor)
.put(Reach.class, new Reach(player))
@@ -294,6 +296,8 @@ public class CheckManager {
}
public void onPacketReceive(final PacketReceiveEvent packet) {
player.processingPacket();
for (PacketCheck check : packetChecks.values()) {
check.onPacketReceive(packet);
}
@@ -309,6 +313,8 @@ public class CheckManager {
}
public void onPacketSend(final PacketSendEvent packet) {
player.processingPacket();
for (PacketCheck check : prePredictionChecks.values()) {
check.onPacketSend(packet);
}
@@ -177,7 +177,7 @@ public class PunishmentManager implements ConfigReloadable {
public void handleViolation(Check check) {
for (PunishGroup group : groups) {
if (group.checks.contains(check)) {
long currentTime = System.currentTimeMillis();
long currentTime = player.now();
group.violations.put(currentTime, check);
// Remove violations older than the defined time in the config
@@ -161,9 +161,9 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
if (isPendingSetback()) return; // Don't spam setbacks
// Only let us full resync once every five seconds to prevent unneeded bukkit load
if (System.currentTimeMillis() - lastWorldResync > 5 * 1000) {
if (player.now() - lastWorldResync > 5 * 1000) {
player.resyncPositions(player.boundingBox.copy().expand(1));
lastWorldResync = System.currentTimeMillis();
lastWorldResync = player.now();
}
Vector3dm clientVel = lastKnownGoodPosition.vector.clone();
@@ -120,13 +120,13 @@ public class GrimPlayer implements GrimUser {
public long lastTransSent = 0;
public long lastTransReceived = 0;
@Getter
private long playerClockAtLeast = System.nanoTime();
private long playerClockAtLeast;
public double lastWasClimbing = 0;
public boolean canSwimHop = false;
public int riptideSpinAttackTicks = 0;
public int powderSnowFrozenTicks = 0;
public boolean hasGravity = true;
public final long joinTime = System.currentTimeMillis();
public final long joinTime;
public boolean playerEntityHasGravity = true;
public VectorData predictedVelocity = new VectorData(new Vector3dm(), VectorData.VectorType.Normal);
public Vector3dm actualMovement = new Vector3dm();
@@ -272,6 +272,12 @@ public class GrimPlayer implements GrimUser {
public boolean lastJumping;
public GrimPlayer(@NotNull User user) {
this(user, System.currentTimeMillis(), System.nanoTime());
}
protected GrimPlayer(@NotNull User user, long joinTime, long clockStart) {
this.joinTime = joinTime;
this.playerClockAtLeast = clockStart;
this.user = user;
this.uuid = user.getUUID();
fireworks = new CompensatedFireworks(this); // Must be before checkmanager
@@ -397,7 +403,7 @@ public class GrimPlayer implements GrimUser {
// Transactions that we send don't count towards total limit
if (viaPacketTracker != null) viaPacketTracker.setIntervalPackets(viaPacketTracker.getIntervalPackets() - 1);
if (skipped > 0 && System.currentTimeMillis() - joinTime > 5000)
if (skipped > 0 && now() - joinTime > 5000)
checkManager.getCheck(TransactionOrder.class).flagAndAlert("skipped: " + skipped);
do {
@@ -406,14 +412,14 @@ public class GrimPlayer implements GrimUser {
break;
lastTransactionReceived.incrementAndGet();
lastTransReceived = System.currentTimeMillis();
transactionPing = (System.nanoTime() - data.second());
lastTransReceived = now();
transactionPing = (this.nano() - data.second());
playerClockAtLeast = data.second();
} while (data.first() != id);
// A transaction means a new tick, so handle any block interactions
CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedBreaks(this, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, now());
CheckManagerListener.handleQueuedBreaks(this, false, 0, 0, now());
latencyUtils.handleNettySyncTransaction(lastTransactionReceived.get());
}
@@ -461,11 +467,11 @@ public class GrimPlayer implements GrimUser {
if (user.getEncoderState() != ConnectionState.PLAY) return;
// Send a packet once every 15 seconds to avoid any memory leaks
if (disableGrim && (System.nanoTime() - getPlayerClockAtLeast()) > 15e9) {
if (disableGrim && (this.nano() - getPlayerClockAtLeast()) > 15e9) {
return;
}
lastTransSent = System.currentTimeMillis();
lastTransSent = now();
short transactionID = (short) (-1 * (transactionIDCounter.getAndIncrement() & 0x7FFF));
try {
@@ -524,15 +530,20 @@ public class GrimPlayer implements GrimUser {
}
public void pollData() {
// Reset the debug timer for the Scheduler Thread.
// In Production: This is a no-op (0 cost).
// In Debug: This tells the clock "I am starting a new tick, don't freeze time."
this.processingPacket();
// Send a transaction at least once a tick, for timer and post check purposes
// Don't be the first to send the transaction, or we will stack overflow
//
// This will only really activate if there's no entities around the player being tracked
// 80 is a magic value that is roughly every other tick, we don't want to spam too many packets.
if (lastTransSent != 0 && lastTransSent + 80 < System.currentTimeMillis()) {
if (lastTransSent != 0 && lastTransSent + 80 < now()) {
sendTransaction(true); // send on netty thread
}
if ((System.nanoTime() - getPlayerClockAtLeast()) > maxTransactionTime * 1e9) {
if ((this.nano() - getPlayerClockAtLeast()) > maxTransactionTime * 1e9) {
timedOut();
}
@@ -988,4 +999,27 @@ public class GrimPlayer implements GrimUser {
return blockStateId;
}
/**
* Returns the current high-resolution time in nanoseconds.
* In Debug Mode, this will subtract time spent paused at breakpoints.
*/
public long nano() {
return System.nanoTime();
}
/**
* Returns the current wall-clock time in milliseconds.
* In Debug Mode, this will subtract time spent paused at breakpoints.
*/
public long now() {
return System.currentTimeMillis();
}
/**
* Called immediately when a packet starts processing.
* Used by DebugGrimPlayer to differentiate "Lag" (Pre-Packet) from "Breakpoints" (Intra-Packet).
* Optimized away (Dead Code Elimination) in production.
*/
public void processingPacket() {}
}
@@ -75,9 +75,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
}
}
long start = System.nanoTime();
long start = player.nano();
check(data);
long length = System.nanoTime() - start;
long length = player.nano() - start;
if (!player.disableGrim) {
predictionNanos = (predictionNanos * 499 / 500d) + (length / 500d);
@@ -352,7 +352,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
boolean clientClaimsRiptide = player.packetStateData.tryingToRiptide;
if (player.packetStateData.tryingToRiptide) {
long currentTime = System.currentTimeMillis();
long currentTime = player.now();
boolean isInWater = player.isInWaterOrRain();
if (currentTime - player.packetStateData.lastRiptide < 450 || !isInWater) {
@@ -3,6 +3,8 @@ package ac.grim.grimac.utils.anticheat;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.event.events.GrimJoinEvent;
import ac.grim.grimac.api.event.events.GrimQuitEvent;
import ac.grim.grimac.checks.debug.DebugGrimPlayer;
import ac.grim.grimac.checks.debug.GrimDebugSettings;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.reflection.GeyserUtil;
import com.github.retrooper.packetevents.PacketEvents;
@@ -66,12 +68,21 @@ public class PlayerDataManager {
public void addUser(final @NotNull User user) {
if (shouldCheck(user)) {
GrimPlayer player = new GrimPlayer(user);
GrimPlayer player = this.createPlayer(user);
playerDataMap.put(user, player);
GrimAPI.INSTANCE.getEventBus().post(new GrimJoinEvent(player));
}
}
public GrimPlayer createPlayer(User user) {
// Simple Global Check
if (GrimDebugSettings.shouldEnable()) {
return new DebugGrimPlayer(user);
} else {
return new GrimPlayer(user);
}
}
public GrimPlayer remove(final @NotNull User user) {
return playerDataMap.remove(user);
}
@@ -29,4 +29,10 @@ public class CommonGrimArguments {
* This setting is opt-in (default: false) and requires a server restart to change.
*/
public final static SystemArgument<Boolean> USE_CHAT_FAST_BYPASS = FACTORY.create(string("ChatFastBypass", true));
/**
* If TRUE, and a Debugger is detected attached to the JVM, Grim will use the
* DebugGrimPlayer implementation which absorbs breakpoint pauses > 200ms.
* Default: false (Safety).
*/
public final static SystemArgument<Boolean> DEBUG_BREAKPOINT_MODE = FACTORY.create(string("DebugBreakpointMode", false));
}