Compare commits

...
Author SHA1 Message Date
Axionize 6b12083427 Cull dead entities from canHit() 2025-04-30 14:18:57 -04:00
Axionize bc6d5e3b8f Add support for Hitbox Debugging 2025-04-30 14:18:57 -04:00
Axionize 7a3e932779 - Re-add HitboxBlock and HitboxEntity (renamed to WallHit and EntityPierce)
- Reduce unnecessary reach vectors used for calculations in versions
2025-04-30 14:18:57 -04:00
Axionize 6eb358e28e Add Inventory checks 2025-04-30 14:18:57 -04:00
Axionize efbc365b67 Add /grim history command to see historical alerts
- Add basic runtime reloading support.
  - Will only actually reload if current database is different/has different setting than the one requested in config.

- Added [log] statements matching [alert] statements
2025-04-30 14:18:57 -04:00
Axionize 36ae27a79a Runtime startup check of PE version 2025-04-30 14:18:57 -04:00
97 changed files with 2979 additions and 148 deletions

No files matched your search

@@ -38,6 +38,7 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains")
relocate("org.incendo", "ac.grim.grimac.shaded.incendo")
relocate("io.leangen.geantyref", "ac.grim.grimac.shaded.geantyref") // Required by cloud
relocate("com.zaxxer", "ac.grim.grimac.shaded.zaxxer") // Database history
}
mergeServiceFiles()
}
@@ -0,0 +1,38 @@
package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class BukkitOfflinePlatformPlayer implements OfflinePlatformPlayer {
private final OfflinePlayer offlinePlayer;
public BukkitOfflinePlatformPlayer(OfflinePlayer offlinePlayer) {
this.offlinePlayer = offlinePlayer;
}
@Override
public boolean isOnline() {
return offlinePlayer.isOnline();
}
@Override
public @NotNull String getName() {
return offlinePlayer.getName();
}
@Override
public @NotNull UUID getUniqueId() {
return offlinePlayer.getUniqueId();
}
@Override
public boolean equals(Object o) {
if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) {
return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId());
}
return false;
}
}
@@ -1,9 +1,12 @@
package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@@ -11,11 +14,17 @@ import java.util.UUID;
public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory<Player> {
@Override
protected Player getNativePlayer(@NotNull UUID uuid) {
return Bukkit.getPlayer(uuid);
}
@Override
protected Player getNativePlayer(@NonNull String name) {
return Bukkit.getPlayer(name);
}
@Override
protected PlatformPlayer createPlatformPlayer(@NotNull Player nativePlayer) {
return new BukkitPlatformPlayer(nativePlayer);
@@ -44,4 +53,16 @@ public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory<P
// Cast Collection<? extends Player> to Collection<Player>
return (Collection<Player>) Bukkit.getOnlinePlayers();
}
@Override
public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
return new BukkitOfflinePlatformPlayer(offlinePlayer);
}
@Override
public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(name);
return new BukkitOfflinePlatformPlayer(offlinePlayer);
}
}
+1
View File
@@ -45,6 +45,7 @@ dependencies {
api(libs.fastutil)
api(libs.adventure.text.minimessage)
api(libs.jetbrains.annotations)
api("com.zaxxer:HikariCP:4.0.3")
api("ac.grim.grimac:GrimAPI:1.1.0.0")
@@ -10,6 +10,7 @@ import ac.grim.grimac.manager.SpectateManager;
import ac.grim.grimac.manager.TickManager;
import ac.grim.grimac.manager.config.BaseConfigManager;
import ac.grim.grimac.manager.init.Initable;
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
import ac.grim.grimac.platform.api.Platform;
import ac.grim.grimac.platform.api.PlatformLoader;
import ac.grim.grimac.platform.api.PlatformServer;
@@ -45,6 +46,7 @@ public final class GrimAPI {
private final TickManager tickManager;
private final EventBus eventBus;
private final GrimExternalAPI externalAPI;
private ViolationDatabaseManager violationDatabaseManager;
private PlatformLoader loader;
private InitManager initManager;
private boolean initialized = false;
@@ -76,6 +78,7 @@ public final class GrimAPI {
public void load(PlatformLoader platformLoader, Initable... platformSpecificInitables) {
this.loader = platformLoader;
this.violationDatabaseManager = new ViolationDatabaseManager(getGrimPlugin());
this.initManager = new InitManager(loader.getPacketEvents(), loader::getCommandManager, platformSpecificInitables);
this.initManager.load();
this.initialized = true;
@@ -181,6 +181,7 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
GrimAPI.INSTANCE.getAlertManager().reload(configManager);
GrimAPI.INSTANCE.getDiscordManager().reload();
GrimAPI.INSTANCE.getSpectateManager().reload();
GrimAPI.INSTANCE.getViolationDatabaseManager().reload();
// Don't reload players if the plugin hasn't started yet
if (!started) return;
// Reload checks for all players
@@ -16,7 +16,7 @@ public abstract class AbstractDebugHandler extends Check {
return grimPlayer;
}
public abstract void toggleListener(GrimPlayer player);
public abstract boolean toggleListener(GrimPlayer player);
public abstract boolean toggleConsoleOutput();
}
@@ -0,0 +1,206 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Handles debug visualization of hitboxes and reach calculations for GrimAC.
* Sends debug data to clients through plugin messages that can be visualized by compatible clients.
*/
public class HitboxDebugHandler extends AbstractDebugHandler {
/**
* Set of players currently listening to hitbox debug data
*/
private final Set<GrimPlayer> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
/**
* Creates a new HitboxDebugHandler for the specified player
*
* @param grimPlayer The GrimAC player instance to debug
*/
public HitboxDebugHandler(GrimPlayer grimPlayer) {
super(grimPlayer);
}
/**
* Toggles whether a player receives hitbox debug visualization data.
* If the player is already listening, they will be removed. If not, they will be added.
*
* @param player The player to toggle debug visualization for
* @return {@code true} if the player is now listening (was added),
* {@code false} if the player is no longer listening (was removed).
*/
@Override
public boolean toggleListener(GrimPlayer player) {
boolean wasPresent = listeners.remove(player);
if (wasPresent) {
return false;
} else {
listeners.add(player);
return true;
}
}
@Override
public boolean toggleConsoleOutput() {
throw new UnsupportedOperationException();
}
/**
* Sends debug visualization data for reach calculations to all listening players.
* The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations.
*
* @param hitboxes Map of entity IDs to their collision boxes
* @param targetEntities Set of entity IDs that are considered targets
* @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights
* @param basePos Base position before eye height adjustments
* @param isPrediction Whether these hitboxes are from a prediction calculation
*
* Packet Format (Version 1):
* - Byte: Version (0)
* - Byte: Global flags
* - Bit 0: isPrediction
* - Bits 1-7: Reserved
* - Double: Player reach/interact distance
* - Vector3d: Base position (3 doubles)
* - VarInt: Number of ray traces
* - For each ray trace:
* - Double: Eye height delta
* - Vector3d: Look vector (3 doubles)
* - VarInt: Number of hitboxes
* - For each hitbox:
* - VarInt: Entity ID
* - Byte: Flags
* - Bit 0: Is target entity
* - Bit 1: Is no collision
* - Bits 2-7: Reserved
* - If not NoCollisionBox:
* - Double: minX
* - Double: minY
* - Double: minZ
* - Double: maxX
* - Double: maxY
* - Double: maxZ
*/
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, Vector3dm basePos,
boolean isPrediction, double reachDistance) {
if (!isEnabled()) return;
ByteBuf buffer = Unpooled.buffer();
try {
// Version Header
buffer.writeByte(1);
// Global Flags Header
// Pack boolean flags into a single byte
byte global_flags = 0;
global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction?
// Bits 2-7 reserved for future use
buffer.writeByte(global_flags);
// Write reach distance
buffer.writeDouble(reachDistance);
// Write base position
writeVector(buffer, basePos);
// Write number of ray traces
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
// Write all possible ray traces
for (Pair<Vector3dm, Double> pair : lookVecsAndEyeHeights) {
Vector3dm lookVec = pair.first();
double eyeHeight = pair.second();
// Write eye height delta and look vector
// we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer
// then they need to be so grim can report correct reach distance for too far away hits
buffer.writeDouble(eyeHeight);
writeVector(buffer, lookVec);
}
// Write number of hitboxes
ByteBufHelper.writeVarInt(buffer, hitboxes.size());
// Write hitbox data
for (Map.Entry<Integer, CollisionBox> entry : hitboxes.entrySet()) {
int entityId = entry.getKey();
CollisionBox box = entry.getValue();
// Write entity ID
ByteBufHelper.writeVarInt(buffer, entityId);
// Pack boolean flags into a single byte
byte flags = 0;
flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target
flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision
// Bits 2-7 reserved for future use
buffer.writeByte(flags);
// Write box coordinates if it's not a NoCollisionBox
if ((flags & 2) == 0) {
SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box;
buffer.writeDouble(simpleCollisionBox.minX);
buffer.writeDouble(simpleCollisionBox.minY);
buffer.writeDouble(simpleCollisionBox.minZ);
buffer.writeDouble(simpleCollisionBox.maxX);
buffer.writeDouble(simpleCollisionBox.maxY);
buffer.writeDouble(simpleCollisionBox.maxZ);
}
}
// Convert buffer to byte array
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
// Create and send packet
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(
"grim:debug_hitbox",
data
);
// Send to all listeners
for (GrimPlayer listener : listeners) {
if (listener != null) {
PacketEvents.getAPI().getPlayerManager().sendPacket(listener.user, packet);
}
}
} finally {
// Release buffer to prevent memory leaks
buffer.release();
}
}
public boolean isEnabled() {
return !listeners.isEmpty();
}
/**
* Helper method to write a Vector to the ByteBuf
* @param buffer The buffer to write to
* @param vector The vector to write
*/
private void writeVector(ByteBuf buffer, Vector3dm vector) {
buffer.writeDouble(vector.getX());
buffer.writeDouble(vector.getY());
buffer.writeDouble(vector.getZ());
}
}
@@ -0,0 +1,13 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "Entity Pierce", configName = "EntityPierce", setback = 30)
public class EntityPierce extends Check implements PacketCheck {
public EntityPierce(GrimPlayer player) {
super(player);
}
}
@@ -18,13 +18,21 @@ package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.BlockHitData;
import ac.grim.grimac.utils.data.EntityHitData;
import ac.grim.grimac.utils.data.HitData;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import ac.grim.grimac.utils.nmsutil.WorldRayTrace;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
@@ -32,16 +40,17 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
// You may not copy the check unless you are licensed under GPL
@CheckData(name = "Reach", setback = 10)
@@ -55,8 +64,14 @@ public class Reach extends Check implements PacketCheck {
// Only one flag per reach attack, per entity, per tick.
// We store position because lastX isn't reliable on teleports.
private final Int2ObjectMap<Vector3d> playerAttackQueue = new Int2ObjectOpenHashMap<>();
// temporarily used to prevent falses in the wall hit check
private final Set<Vector3i> blocksChangedThisTick = new HashSet<>();
// extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit
public static final double extraSearchDistance = 3;
private boolean ignoreNonPlayerTargets;
private boolean cancelImpossibleHits;
private double threshold;
public double threshold;
private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach
public Reach(GrimPlayer player) {
@@ -88,6 +103,10 @@ public class Reach extends Check implements PacketCheck {
return;
}
if (ignoreNonPlayerTargets && !entity.getType().equals(EntityTypes.PLAYER)) {
return;
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
@@ -114,8 +133,9 @@ public class Reach extends Check implements PacketCheck {
}
// If the player set their look, or we know they have a new tick
final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType());
if (isUpdate(event.getPacketType())) {
tickBetterReachCheckWithAngle();
tickBetterReachCheckWithAngle(isFlying);
}
}
@@ -149,7 +169,7 @@ public class Reach extends Check implements PacketCheck {
}
}
private void tickBetterReachCheckWithAngle() {
private void tickBetterReachCheckWithAngle(boolean isFlying) {
for (Int2ObjectMap.Entry<Vector3d> attack : playerAttackQueue.int2ObjectEntrySet()) {
PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey());
if (reachEntity == null) continue;
@@ -164,10 +184,21 @@ public class Reach extends Check implements PacketCheck {
String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added);
}
case WALL_HIT -> {
String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getCheck(WallHit.class).flagAndAlert(result.verbose() + added);
}
case ENTITY_PIERCE -> {
String added = reachEntity.getType() == EntityTypes.PLAYER ? "" : "type=" + reachEntity.getType().getName().getKey();
player.checkManager.getCheck(EntityPierce.class).flagAndAlert(result.verbose() + added);
}
}
}
playerAttackQueue.clear();
// We can't use transactions for this because of this problem:
// transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses
if (isFlying) blocksChangedThisTick.clear();
}
@NotNull
@@ -196,28 +227,15 @@ public class Reach extends Check implements PacketCheck {
double minDistance = Double.MAX_VALUE;
// https://bugs.mojang.com/browse/MC-67665
List<Vector3dm> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot)));
// If we are a tick behind, we don't know their next look so don't bother doing this
if (!isPrediction) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.yRot));
// 1.9+ players could be a tick behind because we don't get skipped ticks
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastXRot, player.lastYRot));
}
// 1.7 players do not have any of these issues! They are always on the latest look vector
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.xRot, player.yRot));
}
}
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
// We only need to check for blocking intersections for these
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights = new ArrayList<>();
final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final Vector3dm[] possibleLookDirs = player.getPossibleLookVectors(isPrediction);
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
for (Vector3dm lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
@@ -233,13 +251,44 @@ public class Reach extends Check implements PacketCheck {
if (intercept != null) {
minDistance = Math.min(eyePos.distance(intercept), minDistance);
lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye));
}
}
}
if (hitboxDebuggingEnabled())
sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction);
HitData foundHitData = null;
// If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case)
// Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check.
if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) {
// we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target...
// I'm too lazy to do that and we don't need to optimize that much yet so...
final @Nullable Pair<Double, HitData> hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, from);
HitData hitData = hitResult.second();
// If the returned hit result was NOT the target entity we flag the check
if (hitData instanceof EntityHitData &&
player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
// until I fix block modeling exempt any blocks changed this tick
} else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).getPosition())) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
}
}
// if the entity is not exempt and the entity is alive
if ((!blacklisted.contains(reachEntity.getType()) && reachEntity.isLivingEntity()) || reachEntity.getType() == EntityTypes.END_CRYSTAL) {
if (minDistance == Double.MAX_VALUE) {
if (minDistance == Double.MIN_VALUE && foundHitData != null) {
cancelBuffer = 1;
if (foundHitData instanceof BlockHitData) {
return new CheckResult(ResultType.WALL_HIT, "Hit block=" + ((BlockHitData) foundHitData).getState().getType().getName() + " ");
} else { // entity hit data
return new CheckResult(ResultType.ENTITY_PIERCE, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().getType().getName() + " ");
}
} else if (minDistance == Double.MAX_VALUE) {
cancelBuffer = 1;
return new CheckResult(ResultType.HITBOX, "");
} else if (minDistance > maxReach) {
@@ -255,12 +304,13 @@ public class Reach extends Check implements PacketCheck {
@Override
public void onReload(ConfigManager config) {
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true);
this.threshold = config.getDoubleElse("Reach.threshold", 0.0005);
}
private enum ResultType {
REACH, HITBOX, NONE
REACH, HITBOX, WALL_HIT, ENTITY_PIERCE, NONE
}
private record CheckResult(ResultType type, String verbose) {
@@ -268,4 +318,69 @@ public class Reach extends Check implements PacketCheck {
return type != ResultType.NONE;
}
}
public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) {
if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this
// Only do this for nearby blocks
if (new Vector3dm(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector3dm(player.x, player.y, player.z)) > 6) return;
// Only do this if the state really had any world impact
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
blocksChangedThisTick.add(vector3i);
}
private boolean hitboxDebuggingEnabled() {
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
}
private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
Map<Integer, CollisionBox> hitboxes = new HashMap<>();
for (Int2ObjectMap.Entry<PacketEntity> entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) {
PacketEntity entity = entry.getValue();
if (!entity.canHit()) continue;
CollisionBox box;
if (entity.equals(reachEntity)) {
// Target entity gets expanded hitbox
box = entity.getPossibleCollisionBoxes();
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
sBox.expand(threshold);
// Add movement threshold uncertainty for 1.9+ or non-position updates
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(player.getMovementThreshold());
}
} else {
// Non-target entities
box = entity.getMinimumPossibleCollisionBoxes();
if (box instanceof NoCollisionBox) {
hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE);
continue;
} else if (box instanceof SimpleCollisionBox) {
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
sBox.expand(-threshold);
// Shrink non-target entities by movement threshold when applicable
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(-player.getMovementThreshold());
}
}
}
// Add 1.8 and below extra hitbox size
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& box instanceof SimpleCollisionBox) {
((SimpleCollisionBox) box).expand(0.1f);
}
hitboxes.put(entry.getIntKey(), box);
}
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
lookVecsAndEyeHeights,
new Vector3dm(from.getX(), from.getY(), from.getZ()),
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
}
}
@@ -0,0 +1,13 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "Wall Hit", configName = "WallHit", setback = 20)
public class WallHit extends Check implements PacketCheck {
public WallHit(GrimPlayer player) {
super(player);
}
}
@@ -0,0 +1,42 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open")
public class InventoryA extends InventoryCheck {
public InventoryA(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
if (wrapper.getAction() != InteractAction.ATTACK) return;
// Is not possible to attack while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission())
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -0,0 +1,35 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open")
public class InventoryB extends InventoryCheck {
public InventoryB(GrimPlayer player) {
super(player);
}
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) {
if (wrapper.getAction() != DiggingAction.START_DIGGING) return;
// Is not possible to start digging a block while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -0,0 +1,30 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open")
public class InventoryC extends InventoryCheck {
public InventoryC(GrimPlayer player) {
super(player);
}
public void onBlockPlace(final BlockPlace place) {
// It is not possible to place a block while the inventory is open
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
if (shouldModifyPackets()) {
place.resync();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -0,0 +1,85 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VectorData.MoveVectorData;
import ac.grim.grimac.utils.data.VehicleData;
import java.util.StringJoiner;
@CheckData(name = "InventoryD", setback = 1, decay = 0.25)
public class InventoryD extends InventoryCheck {
private int horseJumpVerbose;
public InventoryD(GrimPlayer player) {
super(player);
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (!predictionComplete.isChecked() ||
predictionComplete.getData().isTeleport() ||
player.getSetbackTeleportUtil().blockOffsets ||
player.packetStateData.lastPacketWasTeleport ||
player.packetStateData.isSlowedByUsingItem() ||
System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) {
return;
}
if (player.hasInventoryOpen) {
boolean inVehicle = player.inVehicle();
boolean isJumping, isMoving;
if (inVehicle) {
VehicleData vehicle = player.vehicleData;
// Will flag once if player open anything with pressed space bar
isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1;
isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0;
} else {
MoveVectorData move = findMovement(player.predictedVelocity);
isJumping = player.predictedVelocity.isJump();
isMoving = move != null && (move.x != 0 || move.z != 0);
}
if (!isMoving && !isJumping) {
reward();
return;
}
if (flag()) {
if (!isNoSetbackPermission())
closeInventory();
StringJoiner joiner = new StringJoiner(" ");
if (isMoving) joiner.add("moving");
if (isJumping) joiner.add("jumping");
if (inVehicle) joiner.add("inVehicle");
alert(joiner.toString());
}
} else {
horseJumpVerbose = 0;
}
}
private MoveVectorData findMovement(VectorData vectorData) {
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
while (vectorData != null) {
vectorData = vectorData.lastVector;
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
}
return null;
}
}
@@ -0,0 +1,50 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open")
public class InventoryE extends InventoryCheck {
private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID
public InventoryE(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
// It is not possible to change hotbar slots with held item change while the inventory is open
// A container click packet would be sent instead
if (player.hasInventoryOpen) {
if (this.lastTransaction < player.lastTransactionReceived.get()
&& flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.getInventory().needResend = true;
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) {
this.lastTransaction = player.lastTransactionSent.get();
}
}
}
@@ -0,0 +1,46 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true)
public class InventoryF extends InventoryCheck {
public InventoryF(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted
// once we are ahead of ViaVersion
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
}
@@ -0,0 +1,40 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true)
public class InventoryG extends InventoryCheck {
public InventoryG(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.lastPacketWasTeleport) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
WrapperPlayClientEntityAction.Action action = wrapper.getAction();
if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING
|| action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
return;
}
if (player.hasInventoryOpen) {
if (flagAndAlert() && !isNoSetbackPermission()) {
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -133,8 +133,14 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
}
@Override
public void toggleListener(GrimPlayer player) {
if (!listeners.remove(player)) listeners.add(player);
public boolean toggleListener(GrimPlayer player) {
boolean wasPresent = listeners.remove(player);
if (wasPresent) {
return false;
} else {
listeners.add(player);
return true;
}
}
@Override
@@ -0,0 +1,67 @@
package ac.grim.grimac.checks.type;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
public class InventoryCheck extends BlockPlaceCheck implements PacketCheck {
// Impossible transaction ID
protected static final long NONE = Long.MAX_VALUE;
protected long closeTransaction = NONE;
protected int closePacketsToSkip;
public InventoryCheck(GrimPlayer player) {
super(player);
}
@Override
@MustBeInvokedByOverriders
public void onPacketReceive(final PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
// Disallow any clicks if inventory is closing
if (closeTransaction != NONE && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.getInventory().needResend = true;
}
} else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
// Players with high ping can close inventory faster than send transaction back
if (closeTransaction != NONE && closePacketsToSkip-- <= 0) {
closeTransaction = NONE;
}
}
}
public void closeInventory() {
if (closeTransaction != NONE) {
return;
}
int windowId = player.getInventory().openWindowID;
player.user.writePacket(new WrapperPlayServerCloseWindow(windowId));
// Force close inventory on server side
closePacketsToSkip = 1; // Sending close packet to itself, so skip it
PacketEvents.getAPI().getProtocolManager().receivePacket(
player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId)
);
player.sendTransaction();
int transaction = player.lastTransactionSent.get();
closeTransaction = transaction;
player.latencyUtils.addRealTimeTask(transaction, () -> {
if (closeTransaction == transaction) {
closeTransaction = NONE;
}
});
player.user.flushPackets();
}
}
@@ -1,6 +1,7 @@
package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.command.PlayerSelector;
import ac.grim.grimac.platform.api.sender.Sender;
@@ -36,9 +37,16 @@ public class GrimDebug implements BuildableCommand {
.required("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer())
.handler(this::handleConsoleDebug);
Command.Builder<Sender> hitboxDebugCommand = grimCommand
.literal("hitboxdebug", Description.of("Toggle hitbox debug visualization"))
.permission("grim.hitboxdebug")
.optional("target", GrimAPI.INSTANCE.getParserDescriptors().getSinglePlayer(), Description.of("Player to debug (defaults to self if sender is player)"))
.handler(this::handleHitboxDebug);
// Register command
commandManager.command(debugCommand);
commandManager.command(consoleDebugCommand);
commandManager.command(hitboxDebugCommand);
}
private void handleDebug(@NonNull CommandContext<Sender> context) {
@@ -81,6 +89,48 @@ public class GrimDebug implements BuildableCommand {
sender.sendMessage(message);
}
private void handleHitboxDebug(@NonNull CommandContext<Sender> context) {
Sender sender = context.sender();
PlayerSelector playerSelector = context.getOrDefault("target", null);
// Hitbox debug requires a *player* to be the listener (the one seeing the boxes)
if (!sender.isPlayer()) {
sender.sendMessage(MessageUtil.getParsedComponent(sender,
"hitboxdebug-player-only",
"%prefix% &cHitbox debug can only be toggled by players.")
);
return;
}
// Determine the target player whose hitboxes are being debugged
GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer());
if (targetGrimPlayer == null) return;
// Get the sender's GrimPlayer data, as they are the listener
GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId());
if (senderGrimPlayer == null) {
sender.sendMessage(Component.text("Could not find your player data to register as a listener.", NamedTextColor.RED));
return;
}
// Get the HitboxDebugHandler check instance and toggle the listener
HitboxDebugHandler hitboxHandler = targetGrimPlayer.checkManager.getCheck(HitboxDebugHandler.class);
if (hitboxHandler == null) {
sender.sendMessage(Component.text("HitboxDebugHandler check not found for target player.", NamedTextColor.RED));
return;
}
boolean enabled = hitboxHandler.toggleListener(senderGrimPlayer); // Pass the sender/listener
// Send feedback message
Component message = Component.text()
.append(Component.text("Hitbox debug listener for ", NamedTextColor.GRAY))
.append(Component.text(targetGrimPlayer.getName(), NamedTextColor.WHITE))
.append(Component.text(enabled ? " enabled." : " disabled.", NamedTextColor.GRAY))
.build();
sender.sendMessage(message);
}
private @Nullable GrimPlayer parseTarget(@NonNull Sender sender, @Nullable Sender t) {
if (sender.isConsole() && t == null) {
sender.sendMessage(MessageUtil.getParsedComponent(sender, "console-specify-target", "%prefix% &cYou must specify a target as the console!"));
@@ -0,0 +1,103 @@
package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.manager.violationdatabase.Violation;
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.parser.standard.StringParser;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class GrimHistory implements BuildableCommand {
@Override
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("history", "hist")
.permission("grim.help")
.required("target", StringParser.stringParser())
.optional("page", IntegerParser.integerParser())
.permission("grim.history")
.handler(this::handleHistory)
);
}
private void handleHistory(CommandContext<Sender> context) {
Sender sender = context.sender();
String target = context.get("target");
Integer page = context.getOrDefault("page", 1);
if (!GrimAPI.INSTANCE.getViolationDatabaseManager().isEnabled()) {
String msg = GrimAPI.INSTANCE.getConfigManager().getConfig()
.getStringElse("grim-history-disabled",
"%prefix% &cHistory subsystem is disabled!");
sender.sendMessage(MessageUtil.miniMessage(msg));
return;
}
GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(), () -> {
int entriesPerPage = GrimAPI.INSTANCE.getConfigManager().getConfig().getIntElse("history.entries-per-page", 15);
String header = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-header",
"%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)");
String logFormat = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("grim-history-entry",
"%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)");
OfflinePlatformPlayer targetPlayer = GrimAPI.INSTANCE.getPlatformPlayerFactory().getOfflineFromName(target);
ViolationDatabaseManager violations = GrimAPI.INSTANCE.getViolationDatabaseManager();
int logCount = violations.getLogCount(targetPlayer.getUniqueId());
List<Violation> logs = violations.getViolations(targetPlayer.getUniqueId(), page, entriesPerPage);
int maxPages = (int) Math.ceil((float) logCount / entriesPerPage);
sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, header
.replace("%player%", targetPlayer.getName())
.replace("%page%", String.valueOf(page))
.replace("%maxPages%", String.valueOf(maxPages))
)));
for (int i = logs.size() - 1; i >= 0; i--) {
Violation log = logs.get(i);
sender.sendMessage(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(sender, logFormat
.replace("%player%", targetPlayer.getName())
.replace("%check%", log.getCheckName())
.replace("%verbose%", log.getVerbose())
.replace("%vl%", String.valueOf(log.getVl()))
.replace("%timeago%", getTimeAgo(log.getCreatedAt()))
.replace("%server%", log.getServerName())
)));
}
});
}
private String getTimeAgo(Date date) {
long durationMillis = new Date().getTime() - date.getTime();
long days = TimeUnit.MILLISECONDS.toDays(durationMillis);
durationMillis -= TimeUnit.DAYS.toMillis(days);
long hours = TimeUnit.MILLISECONDS.toHours(durationMillis);
durationMillis -= TimeUnit.HOURS.toMillis(hours);
long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis);
durationMillis -= TimeUnit.MINUTES.toMillis(minutes);
long seconds = TimeUnit.MILLISECONDS.toSeconds(durationMillis);
StringBuilder result = new StringBuilder();
if (days > 0) result.append(days).append("d ");
if (hours > 0) result.append(hours).append("h ");
if (minutes > 0) result.append(minutes).append("m ");
if (seconds > 0) result.append(seconds).append("s");
return result.toString().trim();
}
}
@@ -1,6 +1,7 @@
package ac.grim.grimac.events.packets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.impl.inventory.InventoryB;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
@@ -65,7 +66,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void placeWaterLavaSnowBucket(GrimPlayer player, ItemStack held, StateType toPlace, InteractionHand hand) {
HitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true);
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true);
if (data != null) {
BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), held, data);
@@ -287,7 +288,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void placeBucket(GrimPlayer player, InteractionHand hand) {
HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
if (data != null) {
BlockPlace blockPlace = new BlockPlace(player, hand, data.getPosition(), data.getClosestDirection().getFaceValue(), data.getClosestDirection(), ItemStack.EMPTY, data);
@@ -366,7 +367,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void placeLilypad(GrimPlayer player, InteractionHand hand) {
HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
if (data != null) {
// A lilypad cannot replace a fluid
@@ -564,6 +565,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
final DiggingAction action = packet.getAction();
player.checkManager.getPacketCheck(InventoryB.class).handle(event, packet);
if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) {
final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, player.compensatedWorld.getBlock(packet.getBlockPosition()));
@@ -0,0 +1,163 @@
package ac.grim.grimac.events.packets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus.Action;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
public class PacketPlayerWindow extends PacketListenerAbstract {
public PacketPlayerWindow() {
super(PacketListenerPriority.LOWEST);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !event.isCancelled()) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
if (player.hasInventoryOpen && isNearNetherPortal(player)) {
handleInventoryClose(player, InventoryDesyncStatus.NETHER_PORTAL);
}
}
// Client Status is sent in 1.7-1.8
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) {
WrapperPlayClientClientStatus wrapper = new WrapperPlayClientClientStatus(event);
if (wrapper.getAction() == Action.OPEN_INVENTORY_ACHIEVEMENT) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
handleInventoryOpen(player);
}
}
// We need to do this due to 1.9 not sending anymore the inventory action in the Client Status
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
// TODO: Remove this check after we finish the before ViaVersion injection
// Explanation: On 1.7 and 1.8 we have OPEN_INVENTORY_ACHIEVEMENT on CLIENT_STATUS packet
// but after Via translation this information gets lost
// This is a workaround to atleast make our inventory checks work "decently" in 1.8 clients for 1.9+ servers
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
&& player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
handleInventoryOpen(player);
}
if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) {
handleInventoryOpen(player);
}
}
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED);
}
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.RESPAWN) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event);
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
player.sendTransaction();
String legacyType = wrapper.getLegacyType();
int modernType = wrapper.getType();
InventoryDesyncStatus inventoryDesyncStatus = getContainerDesyncStatus(player, legacyType, modernType);
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
() -> {
if (inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
handleInventoryOpen(player);
} else {
handleInventoryClose(player, inventoryDesyncStatus);
}
});
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_HORSE_WINDOW) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
() -> handleInventoryOpen(player));
} else if (event.getPacketType() == PacketType.Play.Server.CLOSE_WINDOW) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
}
}
private void handleInventoryOpen(GrimPlayer player) {
if (!player.hasInventoryOpen) {
player.lastInventoryOpen = System.currentTimeMillis();
}
player.hasInventoryOpen = true;
}
private void handleInventoryClose(GrimPlayer player, InventoryDesyncStatus desyncStatus) {
player.hasInventoryOpen = false;
player.inventoryDesyncStatus = desyncStatus;
}
public InventoryDesyncStatus getContainerDesyncStatus(GrimPlayer player, String legacyType, int modernType) {
// Closing beacon with the cross button cause desync in 1.7-1.8
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) &&
("minecraft:beacon".equals(legacyType) || modernType == 8)) {
return player.inventoryDesyncStatus = InventoryDesyncStatus.BEACON;
}
if (isNearNetherPortal(player)) {
return player.inventoryDesyncStatus = InventoryDesyncStatus.NETHER_PORTAL;
}
return player.inventoryDesyncStatus = InventoryDesyncStatus.NOT_DESYNCED;
}
public boolean isNearNetherPortal(GrimPlayer player) {
// Going inside nether portal with opened inventory cause desync, fixed in 1.12.2
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_12_1) &&
player.pointThreeEstimator.isNearNetherPortal) {
PacketEntitySelf playerEntity = player.compensatedEntities.self;
// Client ignore nether portal if player has passengers or riding an entity
return !playerEntity.inVehicle() && playerEntity.passengers.isEmpty();
}
return false;
}
}
@@ -2,6 +2,7 @@ package ac.grim.grimac.manager;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.AbstractCheck;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.checks.impl.aim.AimDuplicateLook;
import ac.grim.grimac.checks.impl.aim.AimModulo360;
import ac.grim.grimac.checks.impl.aim.processor.AimProcessor;
@@ -16,10 +17,7 @@ import ac.grim.grimac.checks.impl.breaking.PositionBreakA;
import ac.grim.grimac.checks.impl.breaking.PositionBreakB;
import ac.grim.grimac.checks.impl.breaking.RotationBreak;
import ac.grim.grimac.checks.impl.breaking.WrongBreak;
import ac.grim.grimac.checks.impl.combat.Hitboxes;
import ac.grim.grimac.checks.impl.combat.MultiInteractA;
import ac.grim.grimac.checks.impl.combat.MultiInteractB;
import ac.grim.grimac.checks.impl.combat.Reach;
import ac.grim.grimac.checks.impl.combat.*;
import ac.grim.grimac.checks.impl.crash.CrashA;
import ac.grim.grimac.checks.impl.crash.CrashB;
import ac.grim.grimac.checks.impl.crash.CrashC;
@@ -41,6 +39,7 @@ import ac.grim.grimac.checks.impl.exploit.ExploitA;
import ac.grim.grimac.checks.impl.exploit.ExploitB;
import ac.grim.grimac.checks.impl.exploit.ExploitC;
import ac.grim.grimac.checks.impl.groundspoof.NoFall;
import ac.grim.grimac.checks.impl.inventory.*;
import ac.grim.grimac.checks.impl.misc.ClientBrand;
import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation;
import ac.grim.grimac.checks.impl.misc.TransactionOrder;
@@ -167,6 +166,11 @@ public class CheckManager {
.put(BadPacketsU.class, new BadPacketsU(player))
.put(BadPacketsV.class, new BadPacketsV(player))
.put(BadPacketsY.class, new BadPacketsY(player))
.put(InventoryA.class, new InventoryA(player))
.put(InventoryB.class, new InventoryB(player))
.put(InventoryE.class, new InventoryE(player))
.put(InventoryF.class, new InventoryF(player))
.put(InventoryG.class, new InventoryG(player))
.put(MultiActionsA.class, new MultiActionsA(player))
.put(MultiActionsB.class, new MultiActionsB(player))
.put(MultiActionsC.class, new MultiActionsC(player))
@@ -200,6 +204,7 @@ public class CheckManager {
.put(ExplosionHandler.class, new ExplosionHandler(player))
.put(KnockbackHandler.class, new KnockbackHandler(player))
.put(GhostBlockDetector.class, new GhostBlockDetector(player))
.put(InventoryD.class, new InventoryD(player))
.put(Phase.class, new Phase(player))
.put(Post.class, new Post(player))
.put(PacketOrderA.class, new PacketOrderA(player))
@@ -241,6 +246,7 @@ public class CheckManager {
.build();
blockPlaceCheck = new ImmutableClassToInstanceMap.Builder<BlockPlaceCheck>()
.put(InventoryC.class, new InventoryC(player))
.put(InvalidPlaceA.class, new InvalidPlaceA(player))
.put(InvalidPlaceB.class, new InvalidPlaceB(player))
.put(AirLiquidPlace.class, new AirLiquidPlace(player))
@@ -295,6 +301,9 @@ public class CheckManager {
.put(TransactionOrder.class, new TransactionOrder(player))
.put(VehicleC.class, new VehicleC(player))
.put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach
.put(WallHit.class, new WallHit(player))
.put(EntityPierce.class, new EntityPierce(player))
.put(HitboxDebugHandler.class, new HitboxDebugHandler(player))
.build();
allChecks = new ImmutableClassToInstanceMap.Builder<AbstractCheck>()
@@ -4,15 +4,7 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.manager.init.Initable;
import ac.grim.grimac.manager.init.load.LoadableInitable;
import ac.grim.grimac.manager.init.load.PacketEventsInit;
import ac.grim.grimac.manager.init.start.CommandRegister;
import ac.grim.grimac.manager.init.start.JavaVersion;
import ac.grim.grimac.manager.init.start.PacketLimiter;
import ac.grim.grimac.manager.init.start.PacketManager;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.manager.init.start.TAB;
import ac.grim.grimac.manager.init.start.TickRunner;
import ac.grim.grimac.manager.init.start.ViaBackwardsManager;
import ac.grim.grimac.manager.init.start.ViaVersion;
import ac.grim.grimac.manager.init.start.*;
import ac.grim.grimac.manager.init.stop.StoppableInitable;
import ac.grim.grimac.manager.init.stop.TerminatePacketEvents;
import ac.grim.grimac.platform.api.sender.Sender;
@@ -63,6 +55,7 @@ public class InitManager {
.add(GrimAPI.INSTANCE.getAlertManager())
.add(GrimAPI.INSTANCE.getDiscordManager())
.add(GrimAPI.INSTANCE.getSpectateManager())
.add(GrimAPI.INSTANCE.getViolationDatabaseManager())
.add(new JavaVersion())
.add(new ViaVersion())
.add(new TAB())
@@ -139,6 +139,11 @@ public class PunishmentManager implements ConfigReloadable {
switch (command.command) {
case "[webhook]" -> GrimAPI.INSTANCE.getDiscordManager().sendAlert(player, verbose, check.getDisplayName(), vl);
case "[log]" -> {
int vls = (int) group.violations.values().stream().filter((e) -> e == check).count();
String verboseWithoutGl = verbose.replaceAll(" /gl .*", "");
GrimAPI.INSTANCE.getViolationDatabaseManager().logAlert(player, verboseWithoutGl, check.getDisplayName(), vls);
}
case "[proxy]" -> ProxyAlertMessenger.sendPluginMessage(cmd);
case "[alert]" -> {
sentDebug = true;
@@ -10,12 +10,14 @@ import com.github.retrooper.packetevents.protocol.item.enchantment.type.Enchantm
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.PEVersion;
import java.util.concurrent.Executors;
public class PacketEventsInit implements LoadableInitable {
PacketEventsAPI<?> packetEventsAPI;
PEVersion MINIMUM_REQUIRED_PE_VERSION = new PEVersion(2, 8, 0, true);
public PacketEventsInit(PacketEventsAPI<?> packetEventsAPI) {
this.packetEventsAPI = packetEventsAPI;
@@ -25,6 +27,17 @@ public class PacketEventsInit implements LoadableInitable {
public void load() {
LogUtil.info("Loading PacketEvents...");
PacketEvents.setAPI(packetEventsAPI);
if (!checkPacketEventsVersion()) {
LogUtil.error("\n" +
"******************************************************\n" +
"GrimAC requires PacketEvents >= " + MINIMUM_REQUIRED_PE_VERSION +
(MINIMUM_REQUIRED_PE_VERSION.snapshot() ? "-SNAPSHOT" : "") + "\n" +
"Current version: " + PacketEvents.getAPI().getVersion() + "\n" +
"Please update PacketEvents to a compatible version.\n" +
"*****************************************************");
}
PacketEvents.getAPI().getSettings()
.fullStackTrace(true)
.kickOnPacketException(true)
@@ -43,4 +56,33 @@ public class PacketEventsInit implements LoadableInitable {
ParticleTypes.DUST.getName();
}).start();
}
private boolean checkPacketEventsVersion() {
PEVersion current = PacketEvents.getAPI().getVersion();
PEVersion required = MINIMUM_REQUIRED_PE_VERSION;
// If current version is newer, always accept
if (current.isNewerThan(required)) {
return true;
}
// If current version is exactly equal to required (including snapshot status), accept
if (current.major() == required.major()
&& current.minor() == required.minor()
&& current.patch() == required.patch()
&& current.snapshot() == required.snapshot()) {
return true;
}
// If required is a snapshot, accept matching release or snapshot
if (required.snapshot()
&& current.major() == required.major()
&& current.minor() == required.minor()
&& current.patch() == required.patch()) {
return true;
}
// Otherwise, reject
return false;
}
}
@@ -2,20 +2,7 @@ package ac.grim.grimac.manager.init.start;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.SenderRequirement;
import ac.grim.grimac.command.commands.GrimAlerts;
import ac.grim.grimac.command.commands.GrimBrands;
import ac.grim.grimac.command.commands.GrimDebug;
import ac.grim.grimac.command.commands.GrimDump;
import ac.grim.grimac.command.commands.GrimHelp;
import ac.grim.grimac.command.commands.GrimLog;
import ac.grim.grimac.command.commands.GrimPerf;
import ac.grim.grimac.command.commands.GrimProfile;
import ac.grim.grimac.command.commands.GrimReload;
import ac.grim.grimac.command.commands.GrimSendAlert;
import ac.grim.grimac.command.commands.GrimSpectate;
import ac.grim.grimac.command.commands.GrimStopSpectating;
import ac.grim.grimac.command.commands.GrimVerbose;
import ac.grim.grimac.command.commands.GrimVersion;
import ac.grim.grimac.command.commands.*;
import ac.grim.grimac.command.handler.GrimCommandFailureHandler;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
@@ -60,6 +47,7 @@ public class CommandRegister implements StartableInitable {
new GrimProfile().register(commandManager);
new GrimSendAlert().register(commandManager);
new GrimHelp().register(commandManager);
new GrimHistory().register(commandManager);
new GrimReload().register(commandManager);
new GrimSpectate().register(commandManager);
new GrimStopSpectating().register(commandManager);
@@ -1,20 +1,6 @@
package ac.grim.grimac.manager.init.start;
import ac.grim.grimac.events.packets.CheckManagerListener;
import ac.grim.grimac.events.packets.PacketBlockAction;
import ac.grim.grimac.events.packets.PacketEntityAction;
import ac.grim.grimac.events.packets.PacketHidePlayerInfo;
import ac.grim.grimac.events.packets.PacketPingListener;
import ac.grim.grimac.events.packets.PacketPlayerAttack;
import ac.grim.grimac.events.packets.PacketPlayerCooldown;
import ac.grim.grimac.events.packets.PacketPlayerDigging;
import ac.grim.grimac.events.packets.PacketPlayerJoinQuit;
import ac.grim.grimac.events.packets.PacketPlayerRespawn;
import ac.grim.grimac.events.packets.PacketPlayerSteer;
import ac.grim.grimac.events.packets.PacketSelfMetadataListener;
import ac.grim.grimac.events.packets.PacketServerTags;
import ac.grim.grimac.events.packets.PacketServerTeleport;
import ac.grim.grimac.events.packets.ProxyAlertMessenger;
import ac.grim.grimac.events.packets.*;
import ac.grim.grimac.events.packets.worldreader.BasePacketWorldReader;
import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEight;
import ac.grim.grimac.events.packets.worldreader.PacketWorldReaderEighteen;
@@ -30,6 +16,7 @@ public class PacketManager implements StartableInitable {
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit());
PacketEvents.getAPI().getEventManager().registerListener(new PacketPingListener());
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerWindow());
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerDigging());
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerAttack());
PacketEvents.getAPI().getEventManager().registerListener(new PacketEntityAction());
@@ -0,0 +1,131 @@
package ac.grim.grimac.manager.violationdatabase;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.player.GrimPlayer;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class MySQLViolationDatabase implements ViolationDatabase {
private final GrimPlugin plugin;
private HikariDataSource dataSource;
public MySQLViolationDatabase(GrimPlugin plugin, String url, String database, String username, String password) {
this.plugin = plugin;
setupDataSource(url, database, username, password);
}
private void setupDataSource(String url, String database, String username, String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://" + url + "/" + database);
config.setUsername(username);
config.setPassword(password);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.setMaximumPoolSize(10);
config.setAutoCommit(true);
dataSource = new HikariDataSource(config);
}
@Override
public void connect() {
try (Connection connection = dataSource.getConnection()) {
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS violations(" +
"id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, " +
"server VARCHAR(255) NOT NULL, " +
"uuid CHAR(36) NOT NULL, " +
"check_name TEXT NOT NULL, " +
"verbose TEXT NOT NULL, " +
"vl INTEGER NOT NULL, " +
"created_at BIGINT NOT NULL" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid);"
).execute();
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex);
}
}
@Override
public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
try (Connection connection = dataSource.getConnection();
PreparedStatement insertAlert = connection.prepareStatement(
"INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)"
)
) {
insertAlert.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison"));
insertAlert.setString(2, player.getUniqueId().toString());
insertAlert.setString(3, checkName);
insertAlert.setString(4, verbose);
insertAlert.setInt(5, vls);
insertAlert.setLong(6, System.currentTimeMillis());
insertAlert.execute();
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to log alert", ex);
}
}
@Override
public synchronized int getLogCount(UUID player) {
try (Connection connection = dataSource.getConnection();
PreparedStatement countLogs = connection.prepareStatement(
"SELECT COUNT(*) FROM violations WHERE uuid = ?"
)
) {
countLogs.setString(1, player.toString());
ResultSet result = countLogs.executeQuery();
if (result.next()) {
return result.getInt(1);
}
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to count logs", ex);
}
return 0;
}
@Override
public synchronized List<Violation> getViolations(UUID player, int page, int limit) {
try (Connection connection = dataSource.getConnection();
PreparedStatement fetchLogs = connection.prepareStatement(
"SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" +
" WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?"
)
) {
fetchLogs.setString(1, player.toString());
fetchLogs.setInt(2, limit);
fetchLogs.setInt(3, (page - 1) * limit);
return Violation.fromResultSet(fetchLogs.executeQuery());
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to fetch logs", ex);
return null;
}
}
@Override
public void disconnect() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
public boolean sameConfig(String host, String db, String user, String pwd) {
String wantUrl = "jdbc:mysql://" + host + "/" + db;
return wantUrl.equalsIgnoreCase(dataSource.getJdbcUrl())
&& user.equals(dataSource.getUsername())
&& pwd .equals(dataSource.getPassword()); // Hikari stores clear text
}
}
@@ -0,0 +1,17 @@
package ac.grim.grimac.manager.violationdatabase;
import ac.grim.grimac.player.GrimPlayer;
import java.util.List;
import java.util.UUID;
public final class NoOpViolationDatabase implements ViolationDatabase {
public static final NoOpViolationDatabase INSTANCE = new NoOpViolationDatabase();
private NoOpViolationDatabase() {}
@Override public void connect() {}
@Override public void disconnect() {}
@Override public void logAlert(GrimPlayer p, String v, String c, int vl) {}
@Override public int getLogCount(UUID player) { return 0; }
@Override public List<Violation> getViolations(UUID p, int page, int lim) { return List.of(); }
}
@@ -0,0 +1,130 @@
package ac.grim.grimac.manager.violationdatabase;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.player.GrimPlayer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class SQLiteViolationDatabase implements ViolationDatabase {
private final GrimPlugin plugin;
private Connection openConnection;
public SQLiteViolationDatabase(@NotNull GrimPlugin plugin) {
this.plugin = plugin;
}
@Override
public void connect() {
try (Connection connection = getConnection()) {
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS violations(" +
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
"server VARCHAR(255) NOT NULL, " +
"uuid CHARACTER(36) NOT NULL, " +
"check_name TEXT NOT NULL, " +
"verbose TEXT NOT NULL, " +
"vl INTEGER NOT NULL, " +
"created_at BIGINT NOT NULL" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_violations_uuid ON violations(uuid)"
).execute();
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to generate violations database:", ex);
}
}
@Override
public synchronized void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
try (
Connection connection = getConnection();
PreparedStatement insertLog = connection.prepareStatement(
"INSERT INTO violations (server, uuid, check_name, verbose, vl, created_at) VALUES (?, ?, ?, ?, ?, ?)"
)
) {
insertLog.setString(1, GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison"));
insertLog.setString(2, player.getUniqueId().toString());
insertLog.setString(3, verbose);
insertLog.setString(4, checkName);
insertLog.setInt(5, vls);
insertLog.setLong(6, System.currentTimeMillis());
insertLog.executeUpdate();
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to insert violation:", ex);
}
}
public synchronized int getLogCount(UUID player) {
try (
Connection connection = getConnection();
PreparedStatement fetchLogs = connection.prepareStatement(
"SELECT COUNT(*) FROM violations WHERE uuid = ?"
)
) {
fetchLogs.setString(1, player.toString());
ResultSet resultSet = fetchLogs.executeQuery();
if (resultSet.next()) {
return resultSet.getInt(1);
}
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to fetch number of violations:", ex);
}
return 0;
}
@Override
public synchronized List<Violation> getViolations(UUID player, int page, int limit) {
List<Violation> violations = new ArrayList<>();
try (
Connection connection = getConnection();
PreparedStatement fetchLogs = connection.prepareStatement(
"SELECT server, uuid, check_name, verbose, vl, created_at FROM violations" +
" WHERE uuid = ? ORDER BY created_at DESC LIMIT ? OFFSET ?"
)
) {
fetchLogs.setString(1, player.toString());
fetchLogs.setInt(2, limit);
fetchLogs.setInt(3, (page - 1) * limit);
return Violation.fromResultSet(fetchLogs.executeQuery());
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to fetch violations:", ex);
}
return violations;
}
@Override
public void disconnect() {
try {
if (openConnection != null && !openConnection.isClosed()) {
openConnection.close();
}
} catch (SQLException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to close connection", ex);
}
}
protected synchronized Connection getConnection() throws SQLException {
if (openConnection == null || openConnection.isClosed()) {
openConnection = openConnection();
}
return openConnection;
}
protected Connection openConnection() throws SQLException {
return DriverManager.getConnection("jdbc:sqlite:" + plugin.getDataFolder().getAbsolutePath() + File.separator + "violations.sqlite");
}
}
@@ -0,0 +1,38 @@
package ac.grim.grimac.manager.violationdatabase;
import lombok.Data;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Data
public class Violation {
private final String serverName;
private final UUID playerUUID;
private final String checkName;
private final String verbose;
private final int vl;
private final Date createdAt;
public static List<Violation> fromResultSet(ResultSet resultSet) throws SQLException {
List<Violation> violations = new ArrayList<>();
while(resultSet.next()) {
String server = resultSet.getString("server");
UUID player = UUID.fromString(resultSet.getString("uuid"));
String checkName = resultSet.getString("check_name");
String verbose = resultSet.getString("verbose");
int vl = resultSet.getInt("vl");
Date createdAt = new Date(resultSet.getLong("created_at"));
violations.add(new Violation(server, player, checkName, verbose, vl, createdAt));
}
return violations;
}
}
@@ -0,0 +1,20 @@
package ac.grim.grimac.manager.violationdatabase;
import ac.grim.grimac.player.GrimPlayer;
import java.util.List;
import java.util.UUID;
public interface ViolationDatabase {
void connect();
void logAlert(GrimPlayer player, String verbose, String checkName, int vls);
int getLogCount(UUID player);
List<Violation> getViolations(UUID player, int page, int limit);
void disconnect();
}
@@ -0,0 +1,101 @@
package ac.grim.grimac.manager.violationdatabase;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.manager.init.ReloadableInitable;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.player.GrimPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class ViolationDatabaseManager implements StartableInitable, ReloadableInitable {
private final GrimPlugin plugin;
private @NonNull ViolationDatabase database;
public ViolationDatabaseManager(GrimPlugin plugin) {
this.plugin = plugin;
this.database = NoOpViolationDatabase.INSTANCE;
}
@Override
public void start() {
load();
}
@Override
public void reload() {
load();
}
public void load() {
ConfigManager cfg = GrimAPI.INSTANCE.getConfigManager().getConfig();
boolean enabled = cfg.getBooleanElse("history.enabled", false);
String rawType = enabled ? cfg.getStringElse("history.database.type", "SQLITE").toUpperCase() : "NOOP";
switch (rawType) {
case "SQLITE" -> {
if (!(database instanceof SQLiteViolationDatabase)) {
database.disconnect();
try {
// Init sqlite
Class.forName("org.sqlite.JDBC");
this.database = new SQLiteViolationDatabase(plugin);
} catch (ClassNotFoundException e) {
plugin.getLogger().log(Level.SEVERE,
"""
Could not load SQLite driver for /grim history database.
Download the minecraft-sqlite-jdbc mod/plugin for SQLite support, or change history.database.type
Alternatively set history.enabled=false if /grim history support is not desired"""
);
this.database = NoOpViolationDatabase.INSTANCE;
}
database.connect();
}
}
case "MYSQL" -> {
String host = cfg.getStringElse("history.database.host", "localhost:3306");
String db = cfg.getStringElse("history.database.database", "grimac");
String user = cfg.getStringElse("history.database.username", "root");
String pwd = cfg.getStringElse("history.database.password", "password");
if (database instanceof MySQLViolationDatabase mysql
&& mysql.sameConfig(host, db, user, pwd)) {
break; // nothing changed → keep pool
}
database.disconnect();
database = new MySQLViolationDatabase(plugin, host, db, user, pwd);
database.connect();
}
default -> { // NOOP or invalid
if (!(database instanceof NoOpViolationDatabase)) {
database.disconnect();
database = NoOpViolationDatabase.INSTANCE;
}
}
}
}
public void logAlert(GrimPlayer player, String verbose, String checkName, int vls) {
GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(plugin, () -> database.logAlert(player, verbose, checkName, vls));
}
public int getLogCount(UUID player) {
return database.getLogCount(player);
}
public List<Violation> getViolations(UUID player, int page, int limit) {
return database.getViolations(player, page, limit);
}
public boolean isEnabled() {
return !(database instanceof NoOpViolationDatabase);
}
}
@@ -1,15 +1,13 @@
package ac.grim.grimac.platform.api.entity;
import ac.grim.grimac.api.GrimIdentity;
import ac.grim.grimac.platform.api.world.PlatformWorld;
import ac.grim.grimac.utils.math.Location;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface GrimEntity {
UUID getUniqueId();
public interface GrimEntity extends GrimIdentity {
/**
* Eject any passenger.
*
@@ -2,6 +2,7 @@ package ac.grim.grimac.platform.api.player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
@@ -11,9 +12,8 @@ import java.util.UUID;
public abstract class AbstractPlatformPlayerFactory<T> implements PlatformPlayerFactory {
protected final PlatformPlayerCache cache = PlatformPlayerCache.getInstance();
@Override
public @Nullable
final PlatformPlayer getFromUUID(@NonNull UUID uuid) {
@Override @Nullable
public final PlatformPlayer getFromUUID(@NonNull UUID uuid) {
// Check cache first
PlatformPlayer cachedPlayer = cache.getPlayer(uuid);
if (cachedPlayer != null) {
@@ -31,6 +31,18 @@ public abstract class AbstractPlatformPlayerFactory<T> implements PlatformPlayer
return cache.addOrGetPlayer(uuid, platformPlayer);
}
@Override @Nullable
public PlatformPlayer getFromName(@NonNull String name) {
T nativePlayer = getNativePlayer(name);
if (nativePlayer == null) {
return null;
}
// Create new PlatformPlayer and cache it
PlatformPlayer platformPlayer = createPlatformPlayer(nativePlayer);
return cache.addOrGetPlayer(platformPlayer.getUniqueId(), platformPlayer);
}
@Override
public final PlatformPlayer getFromNativePlayerType(@NonNull Object playerObject) {
if (!isNativePlayerType(playerObject)) {
@@ -81,6 +93,8 @@ public abstract class AbstractPlatformPlayerFactory<T> implements PlatformPlayer
*/
protected abstract T getNativePlayer(@NonNull UUID uuid);
protected abstract T getNativePlayer(@NonNull String name);
/**
* Creates a PlatformPlayer instance from the native player object.
*
@@ -118,4 +132,11 @@ public abstract class AbstractPlatformPlayerFactory<T> implements PlatformPlayer
* @return a collection of native player objects
*/
protected abstract Collection<T> getNativeOnlinePlayers();
@Override
public abstract OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid);
@Override
public abstract OfflinePlatformPlayer getOfflineFromName(@NotNull String name);
}
@@ -0,0 +1,10 @@
package ac.grim.grimac.platform.api.player;
import ac.grim.grimac.api.GrimIdentity;
public interface OfflinePlatformPlayer extends GrimIdentity {
boolean isOnline();
String getName();
}
@@ -7,25 +7,21 @@ import com.github.retrooper.packetevents.util.Vector3d;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public interface PlatformPlayer extends GrimEntity {
public interface PlatformPlayer extends GrimEntity, OfflinePlatformPlayer {
void kickPlayer(String textReason);
boolean hasPermission(String s);
boolean hasPermission(String s, boolean defaultIfUnset);
boolean isSneaking();
void setSneaking(boolean b);
boolean hasPermission(String s);
boolean hasPermission(String s, boolean defaultIfUnset);
void sendMessage(String message);
void sendMessage(Component message);
boolean isOnline();
String getName();
void updateInventory();
Vector3d getPosition();
@@ -4,6 +4,12 @@ import java.util.Collection;
import java.util.UUID;
public interface PlatformPlayerFactory {
OfflinePlatformPlayer getOfflineFromUUID(UUID uuid);
OfflinePlatformPlayer getOfflineFromName(String name);
PlatformPlayer getFromName(String name);
PlatformPlayer getFromUUID(UUID uuid);
PlatformPlayer getFromNativePlayerType(Object playerObject);
@@ -35,6 +35,7 @@ import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
import ac.grim.grimac.utils.data.tags.SyncedTags;
import ac.grim.grimac.utils.enums.FluidTag;
import ac.grim.grimac.utils.enums.Pose;
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
import ac.grim.grimac.utils.latency.CompensatedEntities;
import ac.grim.grimac.utils.latency.CompensatedFireworks;
import ac.grim.grimac.utils.latency.CompensatedInventory;
@@ -46,6 +47,7 @@ import ac.grim.grimac.utils.math.TrigHandler;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketSendEvent;
@@ -242,6 +244,9 @@ public class GrimPlayer implements GrimUser {
// possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+
public double[][] possibleEyeHeights = new double[3][];
public int totalFlyingPacketsSent;
public boolean hasInventoryOpen;
public long lastInventoryOpen;
public InventoryDesyncStatus inventoryDesyncStatus;
public Queue<BlockPlaceSnapshot> placeUseItemPackets = new LinkedBlockingQueue<>();
public Queue<BlockBreak> queuedBreaks = new LinkedBlockingQueue<>();
public PlayerBlockHistory blockHistory = new PlayerBlockHistory();
@@ -647,6 +652,48 @@ public class GrimPlayer implements GrimUser {
}
}
// 1.8-1.10.2 specific mouse delay fix (MC-67665)
// https://bugs.mojang.com/browse/MC-67665
// 1.9-1.21.1 specific desync due to skipped ticks
// Players can be a tick behind on both pitch and yaw together
// 1.21.2+ added end tick input packet, fixing skipped tick issues
public Vector3dm[] getPossibleLookVectors(boolean isPrediction) {
// If we are a tick behind, we don't know their next look so only use current
if (isPrediction) {
return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)};
}
// 1.9-1.10.2: All three vectors (normal, mouse delay, tick desync)
if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) &&
this.getClientVersion().isOlderThan(ClientVersion.V_1_11)) {
return new Vector3dm[]{
ReachUtils.getLook(this, this.xRot, this.yRot),
ReachUtils.getLook(this, this.lastXRot, this.yRot),
ReachUtils.getLook(this, this.lastXRot, this.lastYRot)
};
}
// 1.11-1.21.1: Two vectors (normal, tick desync)
else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_11) &&
this.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)) {
return new Vector3dm[]{
ReachUtils.getLook(this, this.xRot, this.yRot),
ReachUtils.getLook(this, this.lastXRot, this.lastYRot)
};
}
// 1.8: Two vectors (normal, mouse delay)
else if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) &&
this.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
return new Vector3dm[]{
ReachUtils.getLook(this, this.xRot, this.yRot),
ReachUtils.getLook(this, this.lastXRot, this.yRot)
};
}
// 1.7 and 1.21.2+: Just normal vector
else {
return new Vector3dm[]{ReachUtils.getLook(this, this.xRot, this.yRot)};
}
}
@Override
public int getTransactionPing() {
return GrimMath.floor(transactionPing / 1e6);
@@ -1,5 +1,6 @@
package ac.grim.grimac.predictionengine;
import ac.grim.grimac.checks.impl.combat.Reach;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
import ac.grim.grimac.utils.collisions.CollisionData;
@@ -20,6 +21,7 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.Getter;
import lombok.Setter;
@@ -106,6 +108,7 @@ public class PointThreeEstimator {
private boolean isNearHorizontalFlowingLiquid = false; // We can't calculate the direction, only a toggle
private boolean isNearVerticalFlowingLiquid = false; // We can't calculate exact values, once again a toggle
private boolean isNearBubbleColumn = false; // We can't calculate exact values once again
public boolean isNearNetherPortal = false; // We can't calculate exact values once again
private int maxPositiveLevitation = Integer.MIN_VALUE; // Positive potion effects [0, 128]
private int minNegativeLevitation = Integer.MAX_VALUE; // Negative potion effects [-127, -1]r
@@ -154,6 +157,8 @@ public class PointThreeEstimator {
isNearFluid = true;
}
player.checkManager.getPacketCheck(Reach.class).handleBlockChange(new Vector3i(x, y, z), state);
if (pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) {
// https://github.com/MWHunter/Grim/issues/613
int controllingEntityId = player.inVehicle() ? player.getRidingVehicleId() : player.entityID;
@@ -249,6 +254,7 @@ public class PointThreeEstimator {
isNearClimbable = false;
isNearBubbleColumn = false;
isNearFluid = false;
isNearNetherPortal = false;
// Check for flowing water
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
@@ -270,6 +276,10 @@ public class PointThreeEstimator {
isNearFluid = true;
}
if (state.getType() == StateTypes.NETHER_PORTAL) {
isNearNetherPortal = true;
}
return false;
});
}
@@ -479,52 +479,63 @@ public class PredictionEngine {
// Order priority (to avoid false positives and false flagging future predictions):
// Knockback and explosions
// 0.03 ticks
// Movement without input
// Normal movement
// First bread knockback and explosions
// Flagging groundspoof
// Flagging flip items
if (a.isExplosion())
aScore -= 5;
aScore -= 10;
if (a.isKnockback())
aScore -= 5;
aScore -= 10;
if (b.isExplosion())
bScore -= 5;
bScore -= 10;
if (b.isKnockback())
bScore -= 5;
bScore -= 10;
if (a.isFirstBreadExplosion())
aScore += 1;
aScore += 2;
if (b.isFirstBreadExplosion())
bScore += 1;
bScore += 2;
if (a.isFirstBreadKb())
aScore += 1;
aScore += 2;
if (b.isFirstBreadKb())
bScore += 1;
bScore += 2;
if (a.isFlipItem())
aScore += 3;
aScore += 6;
if (b.isFlipItem())
bScore += 3;
bScore += 6;
if (a.isZeroPointZeroThree())
aScore -= 1;
aScore -= 2;
if (b.isZeroPointZeroThree())
bScore -= 2;
if (a.isWithInput() || a.isJump())
aScore += 1;
else
aScore -= 1;
if (b.isWithInput() || b.isJump())
bScore += 1;
else
bScore -= 1;
// If the player is on the ground but the vector leads the player off the ground
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
aScore += 2;
aScore += 4;
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
bScore += 2;
bScore += 4;
if (aScore != bScore)
return Integer.compare(aScore, bScore);
@@ -812,9 +823,9 @@ public class PredictionEngine {
continue;
for (int strafe = strafeMin; strafe <= strafeMax; strafe++) {
for (int forward = forwardMin; forward <= forwardMax; forward++) {
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
VectorData result = new VectorData.MoveVectorData(possibleLastTickOutput.vector.clone()
.add(getMovementResultFromInput(player, transformInputsToVector(player, new Vector3dm(strafe, 0, forward)), speed, player.xRot)),
possibleLastTickOutput, VectorData.VectorType.InputResult);
possibleLastTickOutput, VectorData.VectorType.InputResult, forward, strafe);
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
// Signal that we need to flip sneaking bounding box
@@ -9,7 +9,7 @@ import ac.grim.grimac.utils.collisions.blocks.DoorHandler;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.HitData;
import ac.grim.grimac.utils.data.BlockHitData;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.latency.CompensatedWorld;
import ac.grim.grimac.utils.math.GrimMath;
@@ -69,7 +69,7 @@ public class BlockPlace {
@Getter
StateType material;
@Getter
@Nullable HitData hitData;
@Nullable BlockHitData hitData;
@Getter
int faceId;
BlockFace face;
@@ -80,7 +80,7 @@ public class BlockPlace {
@Setter
Vector3f cursor;
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, HitData hitData) {
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i blockPosition, int faceId, BlockFace face, ItemStack itemStack, BlockHitData hitData) {
this.player = player;
this.hand = hand;
this.blockPosition = blockPosition;
@@ -0,0 +1,32 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class BlockHitData extends HitData {
Vector3i position;
WrappedBlockState state;
BlockFace closestDirection;
// public Boolean success;
public BlockHitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state
// , Boolean success
) {
super(blockHitLocation);
this.position = position;
this.closestDirection = closestDirection;
this.state = state;
// this.success = success;
}
public Vector3d getRelativeBlockHitLocation() {
return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ());
}
}
@@ -0,0 +1,23 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.math.Vector3dm;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class EntityHitData extends HitData {
private final PacketEntity entity;
public EntityHitData(PacketEntity packetEntity) {
this(packetEntity, new Vector3dm(packetEntity.trackedServerPosition.getPos().x,
packetEntity.trackedServerPosition.getPos().y,
packetEntity.trackedServerPosition.getPos().z));
}
public EntityHitData(PacketEntity packetEntity, Vector3dm intersectionPoint) {
super(intersectionPoint); // Use actual intersection point
this.entity = packetEntity;
}
}
@@ -1,29 +1,15 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public class HitData {
Vector3i position;
Vector3dm blockHitLocation;
WrappedBlockState state;
BlockFace closestDirection;
public HitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state) {
this.position = position;
public HitData(Vector3dm blockHitLocation) {
this.blockHitLocation = blockHitLocation;
this.closestDirection = closestDirection;
this.state = state;
}
public Vector3d getRelativeBlockHitLocation() {
return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ());
public ac.grim.grimac.utils.math.Vector3dm getBlockHitLocation() {
return this.blockHitLocation;
}
}
@@ -196,6 +196,133 @@ public class ReachInterpolationData {
return minimumInterpLocation;
}
public CollisionBox getOverlapHitboxCombined() {
int interpSteps = getInterpolationSteps();
// Calculate step increments for each axis
double stepMinX = (targetLocation.minX - startingLocation.minX) / (double) interpSteps;
double stepMaxX = (targetLocation.maxX - startingLocation.maxX) / (double) interpSteps;
double stepMinY = (targetLocation.minY - startingLocation.minY) / (double) interpSteps;
double stepMaxY = (targetLocation.maxY - startingLocation.maxY) / (double) interpSteps;
double stepMinZ = (targetLocation.minZ - startingLocation.minZ) / (double) interpSteps;
double stepMaxZ = (targetLocation.maxZ - startingLocation.maxZ) / (double) interpSteps;
// Track the intersection of all expanded hitboxes
double overallMinX = Double.NEGATIVE_INFINITY;
double overallMaxX = Double.POSITIVE_INFINITY;
double overallMinY = Double.NEGATIVE_INFINITY;
double overallMaxY = Double.POSITIVE_INFINITY;
double overallMinZ = Double.NEGATIVE_INFINITY;
double overallMaxZ = Double.POSITIVE_INFINITY;
boolean isFirstStep = true;
for (int step = interpolationStepsLowBound; step <= interpolationStepsHighBound; step++) {
// Compute interpolated position for this step
double currentMinX = startingLocation.minX + (step * stepMinX);
double currentMaxX = startingLocation.maxX + (step * stepMaxX);
double currentMinY = startingLocation.minY + (step * stepMinY);
double currentMaxY = startingLocation.maxY + (step * stepMaxY);
double currentMinZ = startingLocation.minZ + (step * stepMinZ);
double currentMaxZ = startingLocation.maxZ + (step * stepMaxZ);
// Create the collision box for this step's position
// Create boxes for each bottom corner
SimpleCollisionBox[] cornerBoxes = new SimpleCollisionBox[4];
// Bottom corners: (minX,minY,minZ), (maxX,minY,minZ), (minX,minY,maxZ), (maxX,minY,maxZ)
cornerBoxes[0] = new SimpleCollisionBox(currentMinX, currentMinY, currentMinZ,
currentMinX, currentMinY, currentMinZ);
cornerBoxes[1] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMinZ,
currentMaxX, currentMinY, currentMinZ);
cornerBoxes[2] = new SimpleCollisionBox(currentMinX, currentMinY, currentMaxZ,
currentMinX, currentMinY, currentMaxZ);
cornerBoxes[3] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMaxZ,
currentMaxX, currentMinY, currentMaxZ);
// Expand each corner box by entity dimensions
for (SimpleCollisionBox cornerBox : cornerBoxes) {
GetBoundingBox.expandBoundingBoxByEntityDimensions(cornerBox, player, entity);
}
// Get the overlap of the 4 corner boxes
CollisionBox stepOverlap = getOverlapOfBoxes(cornerBoxes);
if (stepOverlap == NoCollisionBox.INSTANCE)
return NoCollisionBox.INSTANCE;
SimpleCollisionBox stepBox = (SimpleCollisionBox) stepOverlap;
// Initialize overall bounds with the first expanded box
if (isFirstStep) {
overallMinX = stepBox.minX;
overallMaxX = stepBox.maxX;
overallMinY = stepBox.minY;
overallMaxY = stepBox.maxY;
overallMinZ = stepBox.minZ;
overallMaxZ = stepBox.maxZ;
isFirstStep = false;
} else {
// Update bounds to the intersection of all expanded boxes
overallMinX = Math.max(overallMinX, stepBox.minX);
overallMaxX = Math.min(overallMaxX, stepBox.maxX);
overallMinY = Math.max(overallMinY, stepBox.minY);
overallMaxY = Math.min(overallMaxY, stepBox.maxY);
overallMinZ = Math.max(overallMinZ, stepBox.minZ);
overallMaxZ = Math.min(overallMaxZ, stepBox.maxZ);
}
// Early exit if the intersection becomes empty
if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) {
return NoCollisionBox.INSTANCE;
}
}
// Check if the final intersection is valid
if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) {
return NoCollisionBox.INSTANCE;
}
return new SimpleCollisionBox(
overallMinX, overallMinY, overallMinZ,
overallMaxX, overallMaxY, overallMaxZ
);
}
private CollisionBox getOverlapOfBoxes(SimpleCollisionBox[] boxes) {
double minX = Double.NEGATIVE_INFINITY;
double maxX = Double.POSITIVE_INFINITY;
double minY = Double.NEGATIVE_INFINITY;
double maxY = Double.POSITIVE_INFINITY;
double minZ = Double.NEGATIVE_INFINITY;
double maxZ = Double.POSITIVE_INFINITY;
boolean first = true;
for (SimpleCollisionBox box : boxes) {
if (first) {
minX = box.minX;
maxX = box.maxX;
minY = box.minY;
maxY = box.maxY;
minZ = box.minZ;
maxZ = box.maxZ;
first = false;
} else {
minX = Math.max(minX, box.minX);
maxX = Math.min(maxX, box.maxX);
minY = Math.max(minY, box.minY);
maxY = Math.min(maxY, box.maxY);
minZ = Math.max(minZ, box.minZ);
maxZ = Math.min(maxZ, box.maxZ);
}
if (minX > maxX || minY > maxY || minZ > maxZ) {
return NoCollisionBox.INSTANCE;
}
}
return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ);
}
public void updatePossibleStartingLocation(SimpleCollisionBox possibleLocationCombined) {
//GrimACBukkitLoaderPlugin.staticGetLogger().info(ChatColor.BLUE + "Updated new starting location as second trans hasn't arrived " + startingLocation);
this.startingLocation = combineCollisionBox(startingLocation, possibleLocationCombined);
@@ -6,13 +6,38 @@ import lombok.Getter;
import java.util.Objects;
public class VectorData {
public static final class MoveVectorData extends VectorData {
public int x;
public int z;
public MoveVectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType, int x, int z) {
super(vector, lastVector, vectorType);
this.x = x;
this.z = z;
if (x != 0 || z != 0) {
addVectorType(VectorType.WithInput);
}
}
public MoveVectorData(Vector3dm vector, VectorType vectorType, int x, int z) {
super(vector, vectorType);
this.x = x;
this.z = z;
if (x != 0 || z != 0) {
addVectorType(VectorType.WithInput);
}
}
}
public VectorType vectorType;
public VectorData lastVector;
public VectorData preUncertainty;
public Vector3dm vector;
@Getter
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false;
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false, isWithInput = false;
// For handling replacing the type of vector it is while keeping data
public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) {
@@ -33,6 +58,7 @@ public class VectorData {
isJump = lastVector.isJump;
preUncertainty = lastVector.preUncertainty;
isAttackSlow = lastVector.isAttackSlow;
isWithInput = lastVector.isWithInput;
}
addVectorType(vectorType);
@@ -78,6 +104,7 @@ public class VectorData {
case Flip_Use_Item -> isFlipItem = true;
case Jump -> isJump = true;
case AttackSlow -> isAttackSlow = true;
case WithInput -> isWithInput = true;
}
}
@@ -104,6 +131,7 @@ public class VectorData {
Explosion,
FirstBreadExplosion,
InputResult,
WithInput,
StuckMultiplier,
Spectator,
Dead,
@@ -16,6 +16,7 @@
package ac.grim.grimac.utils.data.packetentity;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.ReachInterpolationData;
import ac.grim.grimac.utils.data.TrackedPosition;
@@ -213,6 +214,14 @@ public class PacketEntity extends TypedPacketEntity {
return ReachInterpolationData.combineCollisionBox(oldPacketLocation.getPossibleHitboxCombined(), newPacketLocation.getPossibleHitboxCombined());
}
public CollisionBox getMinimumPossibleCollisionBoxes() {
if (oldPacketLocation == null) {
return newPacketLocation.getOverlapHitboxCombined();
}
return ReachInterpolationData.getOverlapHitbox(oldPacketLocation.getOverlapHitboxCombined(), newPacketLocation.getOverlapHitboxCombined());
}
public PacketEntity getRiding() {
return riding;
}
@@ -0,0 +1,7 @@
package ac.grim.grimac.utils.inventory;
public enum InventoryDesyncStatus {
BEACON,
NETHER_PORTAL,
NOT_DESYNCED
}
@@ -48,7 +48,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
public boolean isPacketInventoryActive = true;
public boolean needResend = false;
public int stateID = 0; // Don't mess up the last sent state ID by changing it
int openWindowID = 0;
public int openWindowID = 0;
// Special values:
// Player inventory is -1
// Unsupported inventory is -2
@@ -1,12 +1,19 @@
package ac.grim.grimac.utils.nmsutil;
import ac.grim.grimac.checks.impl.combat.Reach;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.CollisionData;
import ac.grim.grimac.utils.collisions.HitboxData;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.BlockHitData;
import ac.grim.grimac.utils.data.EntityHitData;
import ac.grim.grimac.utils.data.HitData;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.TypedPacketEntity;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
@@ -16,13 +23,15 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
public class WorldRayTrace {
public static HitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
public static BlockHitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z);
Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ());
Ray trace = new Ray(player, startingPos.getX(), startingPos.getY(), startingPos.getZ(), player.xRot, player.yRot);
@@ -57,7 +66,7 @@ public class WorldRayTrace {
}
}
if (bestHitLoc != null) {
return new HitData(vector3i, bestHitLoc, bestFace, block);
return new BlockHitData(vector3i, bestHitLoc, bestFace, block);
}
if (sourcesHaveHitbox &&
@@ -70,7 +79,7 @@ public class WorldRayTrace {
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
if (intercept.first() != null) {
return new HitData(vector3i, intercept.first(), intercept.second(), block);
return new BlockHitData(vector3i, intercept.first(), intercept.second(), block);
}
}
@@ -83,7 +92,7 @@ public class WorldRayTrace {
//
// I do have to admit that I'm starting to like bifunctions/new java 8 things more than I originally did.
// although I still don't understand Mojang's obsession with streams in some of the hottest methods... that kills performance
public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction<WrappedBlockState, Vector3i, HitData> predicate) {
public static BlockHitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction<WrappedBlockState, Vector3i, BlockHitData> predicate) {
// I guess go back by the collision epsilon?
double endX = GrimMath.lerp(-1.0E-7D, end.x, start.x);
double endY = GrimMath.lerp(-1.0E-7D, end.y, start.y);
@@ -99,7 +108,7 @@ public class WorldRayTrace {
if (start.equals(end)) return null;
WrappedBlockState state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ);
HitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
BlockHitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
if (apply != null) {
return apply;
@@ -148,4 +157,162 @@ public class WorldRayTrace {
return null;
}
@Nullable
public static HitData getNearestHitResult(GrimPlayer player, PacketEntity targetEntity, Vector3dm eyePos, Vector3dm lookVec) {
double maxAttackDistance = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
double maxBlockDistance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
Vector3d startingPos = new Vector3d(eyePos.getX(), eyePos.getY(), eyePos.getZ());
Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ());
Ray trace = new Ray(eyePos, lookVec);
Vector3dm endVec = trace.getPointAtDistance(maxBlockDistance);
Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ());
// Get block hit
BlockHitData blockHitData = getTraverseResult(player, null, startingPos, startingVec, trace, endPos, false, true, maxBlockDistance, true);
Vector3dm closestHitVec = null;
PacketEntity closestEntity = null;
double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance;
for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(PacketEntity::canHit).toList()) {
SimpleCollisionBox box = null;
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
// Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong.
if (entity.equals(targetEntity)) {
box = entity.getPossibleCollisionBoxes();
box.expand(player.checkManager.getPacketCheck(Reach.class).threshold);
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
//
// Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement
if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9))
box.expand(player.getMovementThreshold());
if (ReachUtils.isVecInside(box, eyePos)) {
return new EntityHitData(entity, eyePos);
}
} else {
CollisionBox b = entity.getMinimumPossibleCollisionBoxes();
if (b instanceof NoCollisionBox) {
continue;
}
box = (SimpleCollisionBox) b;
box.expand(-player.checkManager.getPacketCheck(Reach.class).threshold);
// todo, shrink by reachThreshold as well for non-target entities?
if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9))
box.expand(-player.getMovementThreshold());
}
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
box.expand(0.1f);
}
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(Math.sqrt(closestDistanceSquared)));
if (intercept.first() != null) {
double distSquared = intercept.first().distanceSquared(startingVec);
if (distSquared < closestDistanceSquared) {
closestDistanceSquared = distSquared;
closestHitVec = intercept.first();
closestEntity = entity;
}
}
}
return closestEntity == null ? blockHitData : new EntityHitData(closestEntity, closestHitVec);
}
// Checks if it was possible to hit a target entity
// TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction
// NOTE: It should be impossible for the returned Pair to be null
// because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity
// in previous parts of this check when we didn't check for any obstructions like blocks/entities
public static @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(GrimPlayer player, PacketEntity targetEntity,
List<Pair<Vector3dm, Double>> possibleLookVecsAndEyeHeights,
Vector3d from) {
HitData firstObstruction = null;
double firstObstructionDistanceSq = 0;
// Check every possible look direction and every possible eye height
for (Pair<Vector3dm, Double> vectorDoublePair : possibleLookVecsAndEyeHeights) {
Vector3dm lookVec = vectorDoublePair.first();
double eye = vectorDoublePair.second();
Vector3dm eyes = new Vector3dm(from.getX(), from.getY() + eye, from.getZ());
// this function is completely 0.03 aware
final HitData hitResult = WorldRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec);
// If we hit the target entity, it's a valid hit
if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) {
double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation());
return new Pair<>(distanceSquared, hitResult); // Legitimate hit
} else if (hitResult != null && firstObstruction == null) {
// Store the first obstruction only
firstObstruction = hitResult;
firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation());
}
}
// Return the first obstruction if no valid hit found
// Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction
assert firstObstruction != null;
return new Pair<>(firstObstructionDistanceSq, firstObstruction);
}
// TODO replace shrinkBlocks boolean with a data structure/better way to represent
// 1. We have a target block. Shrink everything by movementThreshold except expand target block (we are checking to see if it matches the target block)
// 2. We do not have a target block. Shrink everything by movementThreshold()
// 3. Do not expand or shrink everything, we do not expect 0.03/0.002 or we legacy example where we want to keep old behaviour
private static BlockHitData getTraverseResult(GrimPlayer player, @Nullable StateType heldItem, Vector3d startingPos, Vector3dm startingVec, Ray trace, Vector3d endPos, boolean sourcesHaveHitbox, boolean checkInside, double knownDistance, boolean shrinkBlocks) {
return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> {
// even though sometimes we are raytracing against a block that is the target block, we pass false to this function because it only applies a change for brewing stands in 1.8
CollisionBox data = HitboxData.getBlockHitbox(player, heldItem, player.getClientVersion(), block, false, vector3i.getX(), vector3i.getY(), vector3i.getZ());
SimpleCollisionBox[] boxes = new SimpleCollisionBox[ComplexCollisionBox.DEFAULT_MAX_COLLISION_BOX_SIZE];
int size = data.downCast(boxes);
double bestHitResult = Double.MAX_VALUE;
Vector3dm bestHitLoc = null;
BlockFace bestFace = null;
for (int i = 0; i < size; i++) {
if (shrinkBlocks) boxes[i].expand(-player.getMovementThreshold());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(boxes[i], trace.getOrigin(), trace.getPointAtDistance(knownDistance));
if (intercept.first() == null) continue; // No intercept
Vector3dm hitLoc = intercept.first();
// If inside a block, return empty result for reach check (don't bother checking this?)
if (checkInside && ReachUtils.isVecInside(boxes[i], trace.getOrigin())) {
return null;
}
if (hitLoc.distanceSquared(startingVec) < bestHitResult) {
bestHitResult = hitLoc.distanceSquared(startingVec);
bestHitLoc = hitLoc;
bestFace = intercept.second();
}
}
if (bestHitLoc != null) {
return new BlockHitData(vector3i, bestHitLoc, bestFace, block);
}
if (sourcesHaveHitbox &&
(player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ())
|| player.compensatedWorld.getLavaFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()) == (8 / 9f))) {
double waterHeight = player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ());
SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(knownDistance));
if (intercept.first() != null) {
return new BlockHitData(vector3i, intercept.first(), intercept.second(), block);
}
}
return null;
});
}
}
+16
View File
@@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000
# Dies verhindert, dass Spieler mit hoher Latenz einen einzigen Feuerwerks‑Boost mit Elytra unbegrenzt nutzen können.
max-ping-firework-boost: 1000
history:
enabled: true
# Wie viele Einträge sollen pro Seite mit /grim history <player> angezeigt werden
entries-per-page: 15
# Welcher Servername soll für den History‑Befehl eingetragen werden? Nützlich, wenn dieselbe Datenbank für mehrere Server verwendet wird
server-name: Prison
database:
# SQLITE für lokale Speicherung, MYSQL für eine externe MySQL‑Datenbank. Wird nur nach Serverneustart aktualisiert
type: SQLITE
# MySQL‑Verbindungsdetails
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000
# This prevents high latency players from being able to use 1 firework boost with an elytra forever.
max-ping-firework-boost: 1000
history:
enabled: true
# How many entries should be shown for each page with /grim history <player>
entries-per-page: 15
# What should the inserted server name be for the history command? This is useful if you use the same database for multiple servers
server-name: Prison
database:
# Use SQLITE for local storage, use MYSQL if you have an external MySQL database. This is only updated on server restart
type: SQLITE
# MySQL connection details
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -201,4 +201,20 @@ max-ping-out-of-flying: 1000
# Esto evita que los jugadores con alta latencia puedan usar un solo impulso con fuegos artificiales y una elytra para siempre.
max-ping-firework-boost: 1000
history:
enabled: true
# Cuántas entradas se mostrarán por página con /grim history <player>
entries-per-page: 15
# ¿Qué nombre de servidor debe insertarse para el comando history? Útil si compartes la misma base de datos entre varios servidores
server-name: Prison
database:
# Usa SQLITE para almacenamiento local o MYSQL si tienes una base de datos MySQL externa. Solo se actualiza al reiniciar el servidor
type: SQLITE
# Detalles de conexión MySQL
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -196,4 +196,20 @@ max-ping-out-of-flying: 1000
# Cela empêche les joueurs à forte latence d’utiliser indéfiniment un seul boost de feu d’artifice avec une élytra.
max-ping-firework-boost: 1000
history:
enabled: true
# Combien d’entrées doivent être affichées par page avec /grim history <player>
entries-per-page: 15
# Quel nom de serveur doit être inséré pour la commande history ? Utile si vous utilisez la même base de données pour plusieurs serveurs
server-name: Prison
database:
# Utilisez SQLITE pour un stockage local, MYSQL si vous disposez d’une base MySQL externe. Mis à jour seulement au redémarrage du serveur
type: SQLITE
# Détails de connexion MySQL
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -175,4 +175,20 @@ max-ping-out-of-flying: 1000
# Impedisce ai giocatori con alta latenza di usare all’infinito un solo boost con fuochi d’artificio ed elytra.
max-ping-firework-boost: 1000
history:
enabled: true
# Quante voci devono essere mostrate per pagina con /grim history <player>
entries-per-page: 15
# Quale nome server deve essere inserito per il comando history? Utile se usi lo stesso database per più server
server-name: Prison
database:
# Usa SQLITE per l’archiviazione locale, MYSQL se hai un database MySQL esterno. Aggiornato solo al riavvio del server
type: SQLITE
# Dettagli di connessione MySQL
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -208,4 +208,20 @@ max-ping-out-of-flying: 1000
# これにより、レイテンシの高いプレイヤーが1つのロケット花火による加速でエリトラを永久に使用するのを防ぎます。
max-ping-firework-boost: 1000
history:
enabled: true
# /grim history <player> で 1 ページに表示するエントリー数
entries-per-page: 15
# history コマンドで挿入されるサーバー名は? 複数サーバーで同じデータベースを使用する場合に便利
server-name: Prison
database:
# ローカル保存には SQLITE、外部 MySQL データベースには MYSQL を使用します。サーバー再起動時にのみ反映
type: SQLITE
# MySQL 接続情報
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000
# Dit voorkomt dat spelers met hoge latency oneindig één vuurwerkboost met een elytra kunnen gebruiken.
max-ping-firework-boost: 1000
history:
enabled: true
# Hoeveel items worden per pagina weergegeven met /grim history <player>
entries-per-page: 15
# Welke servernaam moet voor het history‑commando worden ingevoerd? Handig als je dezelfde database voor meerdere servers gebruikt
server-name: Prison
database:
# Gebruik SQLITE voor lokale opslag, MYSQL voor een externe MySQL‑database. Alleen bijgewerkt na een serverherstart
type: SQLITE
# MySQL‑verbindingsgegevens
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -204,4 +204,20 @@ max-ping-out-of-flying: 1000
# Previne jogadores com ping alto de usarem um foguete ara voar indefinidamente.
max-ping-firework-boost: 1000
history:
enabled: true
# Quantas entradas devem ser mostradas por página com /grim history <player>
entries-per-page: 15
# Qual nome de servidor deve ser inserido no comando history? Útil se você usa o mesmo banco de dados para vários servidores
server-name: Prison
database:
# Use SQLITE para armazenamento local ou MYSQL para um banco MySQL externo. Atualizado somente ao reiniciar o servidor
type: SQLITE
# Detalhes de conexão MySQL
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -195,4 +195,20 @@ max-ping-out-of-flying: 1000
# Это предотвращает возможность для игроков с большим пингом бесконечно использовать один фейерверк‑буст с элитрой.
max-ping-firework-boost: 1000
history:
enabled: true
# Сколько записей показывать на странице команды /grim history <player>
entries-per-page: 15
# Какое имя сервера вставлять для команды history? Полезно при использовании одной базы для нескольких серверов
server-name: Prison
database:
# Используйте SQLITE для локального хранения или MYSQL для внешней MySQL‑БД. Изменения применяются после перезапуска сервера
type: SQLITE
# Параметры подключения MySQL
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -199,4 +199,20 @@ max-ping-out-of-flying: 1000
# Bu, yüksek gecikmeli oyuncuların bir havai fişek ivmesini elytra ile sonsuza kadar kullanmalarını engeller.
max-ping-firework-boost: 1000
history:
enabled: true
# /grim history <player> ile her sayfada kaç kayıt gösterilsin
entries-per-page: 15
# History komutu için eklenecek sunucu adı nedir? Aynı veritabanını birden fazla sunucuda kullanıyorsanız faydalıdır
server-name: Prison
database:
# Yerel saklama için SQLITE, harici MySQL veritabanı için MYSQL kullanın. Sadece sunucu yeniden başlatıldığında güncellenir
type: SQLITE
# MySQL bağlantı bilgileri
host: localhost
port: 3306
database: grim
username: root
password: ""
config-version: 9
+16
View File
@@ -202,5 +202,21 @@ max-ping-out-of-flying: 1000
# 填写-1则关闭
max-ping-firework-boost: 1000
history:
enabled: true
# 使用 /grim history <player> 时每页显示多少条记录
entries-per-page: 15
# history 指令中插入的服务器名称?如果多个服务器共用同一数据库则很有用
server-name: Prison
database:
# 本地存储使用 SQLITE,外部数据库使用 MYSQL。仅在重启服务器后生效
type: SQLITE
# MySQL 连接详情
host: localhost
port: 3306
database: grim
username: root
password: ""
# 不要调整这个
config-version: 9
@@ -60,4 +60,11 @@ help:
- "/grim spectate <player> &f- &7Beobachte einen Spieler"
- "/grim verbose &f- &7Zeigt dir jeden Verstoß ohne Puffer an"
- "/grim log [0-255] &f- &7Lädt ein Debug-Log für Vorhersagefehler hoch"
- "/grim history <player> [Seite] &f- &7Zeigt frühere Warnungen für den Spieler"
- "&7======================"
grim-history-disabled: "%prefix% &cDas History‑Subsystem ist deaktiviert!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bZeige Logs für &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bFehlgeschlagen &f%check% (x&c%vl%&f) &7%verbose% (&bvor %timeago%&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <player> &f- &7Spectate a player"
- "/grim verbose &f- &7Shows every flag to you, without buffers"
- "/grim log [0-255] &f- &7Uploads a debug log for prediction flags"
- "/grim history <player> [page] &f- &7Shows previous alerts for the player"
- "&7======================"
grim-history-disabled: "%prefix% &cHistory subsystem is disabled!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bShowing logs for &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bFailed &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% ago&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <player> &f- &7Espectar a un jugador"
- "/grim verbose &f- &7Te muestra todo aviso, sin buffers"
- "/grim log [0-255] &f- &7Sube un registro de depuración para avisos de predicciones"
- "/grim history <player> [página] &f- &7Muestra alertas previas del jugador"
- "&7======================"
grim-history-disabled: "%prefix% &c¡El subsistema de historial está deshabilitado!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bMostrando registros de &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bFallido &f%check% (x&c%vl%&f) &7%verbose% (&bhace %timeago%&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <player> &f- &7Regarder un joueur"
- "/grim verbose &f- &7Affiche chaqu'une de vos violations, sans tampons"
- "/grim log [0-255] &f- &7Téléverse un journal de débogage pour les indicateurs de prédiction"
- "/grim history <player> [page] &f- &7Affiche les alertes précédentes du joueur"
- "&7======================"
grim-history-disabled: "%prefix% &cLe sous‑système d’historique est désactivé !"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bAffichage des journaux pour &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bÉchec &f%check% (x&c%vl%&f) &7%verbose% (&bil y a %timeago%&7)"
@@ -53,4 +53,11 @@ help:
- "/grim spectate <giocatore> &f- &7Osserva un giocatore"
- "/grim verbose &f- &7Mostra ogni segnalazione a te, senza buffer"
- "/grim log [0-255] &f- &7Carica un registro di debug per le segnalazioni di previsione"
- "/grim history <player> [pagina] &f- &7Mostra gli avvisi precedenti del giocatore"
- "&7======================"
grim-history-disabled: "%prefix% &cIl sottosistema cronologia è disabilitato!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bMostrando log per &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bFallito &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% fa&7)"
@@ -58,4 +58,11 @@ help:
- "/grim spectate <player> &f- &7プレイヤーを観戦します"
- "/grim verbose &f- &7全てのフラグをバッファなしで表示します"
- "/grim log [0-255] &f- &7予測フラグのデバッグログをアップロードします"
- "/grim history <player> [ページ] &f- &7プレイヤーの過去のアラートを表示します"
- "&7======================"
grim-history-disabled: "%prefix% &c履歴サブシステムは無効です!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &b%player% のログを表示中 (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &b失敗 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <player> &f- &7Een speler bekijken"
- "/grim verbose &f- &7Toont elke flag, zonder buffers"
- "/grim log [0-255] &f- &7Uploadt een debug-log voor voorspellings-flaggen"
- "/grim history <player> [pagina] &f- &7Toont eerdere waarschuwingen voor de speler"
- "&7======================"
grim-history-disabled: "%prefix% &cHet geschiedenissubsysteem is uitgeschakeld!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bLogbestanden tonen voor &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bMislukt &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% geleden&7)"
@@ -59,4 +59,11 @@ help:
- "/grim spectate <player> &f- &7Especta um jogador."
- "/grim verbose &f- &7Esconde as informações extra."
- "/grim log [0-255] &f- &7Envia o registro da simulação."
- "/grim history <player> [página] &f- &7Mostra alertas anteriores do jogador"
- "&7======================"
grim-history-disabled: "%prefix% &cO subsistema de histórico está desativado!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bMostrando logs de &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bFalhou &f%check% (x&c%vl%&f) &7%verbose% (&bhá %timeago%&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <игрок> &f- &7Наблюдать за игроком"
- "/grim verbose &f- &7Показывает все флаги без буферов"
- "/grim log [0-255] &f- &7Загружает журнал отладки для флагов предсказания"
- "/grim history <player> [страница] &f- &7Показывает предыдущие предупреждения игрока"
- "&7======================"
grim-history-disabled: "%prefix% &cПодсистема истории отключена!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bПоказ журналов для &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bПровалено &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% назад&7)"
+6 -2
View File
@@ -61,7 +61,11 @@ help:
- "/grim spectate (oyuncu) &f- &7Bir oyuncuyu izlemenizi sağlar"
- "/grim verbose &f- &7Arabelleğe almadan tüm uyarıları görmenizi sağlar"
- "/grim log (0-255) &f- &7Uyarılar için bir debug logu çıktısı verir"
- "/grim history <player> [sayfa] &f- &7Oyuncunun önceki uyarılarını gösterir"
- "&7======================"
# Translated by Kayera
# Have a nice day ;)
grim-history-disabled: "%prefix% &cGeçmiş alt sistemi devre dışı!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &bKayıtlar gösteriliyor: &f%player% (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &bBaşarısız &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% önce&7)"
@@ -61,4 +61,11 @@ help:
- "/grim spectate <player> &f- &7观看玩家"
- "/grim verbose &f- &7显示无缓冲区的每个拉回"
- "/grim log [1-999] &f- &7预测标志的调试日志"
- "/grim history <player> [页码] &f- &7显示玩家之前的警报"
- "&7======================"
grim-history-disabled: "%prefix% &c历史子系统已禁用!"
# Valid placeholders: %prefix% %player% %page% %maxPages%
grim-history-header: "%prefix% &b显示 &f%player% 的日志 (&f%page%&b/&f%maxPages%&f)"
# Valid placeholders: %prefix% %check% %vl% %verbose% %timeago% %server%
grim-history-entry: "%prefix% &8[&f%server%&8] &b失败 &f%check% (x&c%vl%&f) &7%verbose% (&b%timeago% 前&7)"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# Ab 2.2.10 gibt es keine AutoClicker-Prüfungen mehr und dies ist ein Platzhalter. Grim wird in Zukunft AutoClicker-Prüfungen einbauen.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# As of 2.2.10, there are no AutoClicker checks and this is a placeholder. Grim will include AutoClicker checks in the future.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# A partir de 2.2.10, no hay ninguna comprobación de AutoClicker y esto es un placeholder.
# Grim incluirá revisiones de AutoClicker en el futuro.
Autoclicker:
@@ -106,3 +141,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# A partir de la version 2.2.10, il n'y a plus de vérifications pour AutoClicker et c'est un placeholder. Grim inclura des vérifications AutoClicker dans le futur.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -16,6 +16,7 @@ Punishments:
- "NoFall"
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -25,6 +26,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -33,6 +35,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -43,14 +46,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -59,8 +73,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -75,6 +108,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -85,9 +119,11 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
Autoclicker:
remove-violations-after: 300
checks:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -97,6 +131,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# バージョン2.2.10時点では、AutoClickerのチェックはまだなく、これはプレースホルダーです。Grimは将来的にAutoClickerのチェックを追加予定です。
Autoclicker:
remove-violations-after: 300
@@ -104,3 +139,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# Vanaf 2.2.10 zijn er geen AutoClicker-controles en is dit een placeholder. Grim zal in de toekomst AutoClicker-controles toevoegen.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# Dês da 2.2.10, não temos verificações de AutoClicker, isso é somente um placeholder. Grim vai verificar por AutoClicker no futuro.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# По состоянию на 2.2.10 нет проверок AutoClicker, и это место для размещения. Grim будет включать проверки AutoClicker в будущем.
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -27,6 +27,7 @@ Punishments:
# Kullanıcı 100 işaretine ulaştığında çalışır ve bundan sonra, 100'ün üzerindeki her 50. işarette çalışır
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -36,6 +37,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -44,6 +46,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -54,14 +57,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -70,8 +84,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -86,6 +119,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -96,6 +130,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# 2.2.10 itibarıyla, AutoClicker denetimleri yoktur ve bu bir yer tutucudur. Grim, gelecekte AutoClicker denetimlerini dahil edecektir.
Autoclicker:
remove-violations-after: 300
@@ -103,3 +138,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -29,6 +29,7 @@ Punishments:
#
commands:
- "100:40 [alert]"
- "100:40 [log]"
- "100:100 [webhook]"
- "100:100 [proxy]"
Knockback:
@@ -38,6 +39,7 @@ Punishments:
- "Explosion"
commands:
- "5:5 [alert]"
- "5:5 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Post:
@@ -46,6 +48,7 @@ Punishments:
- "Post"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
BadPackets:
@@ -56,14 +59,25 @@ Punishments:
- "Crash"
commands:
- "20:20 [alert]"
- "20:20 [log]"
- "40:40 [webhook]"
- "40:40 [proxy]"
Inventory:
remove-violations-after: 300
checks:
- "Inventory"
commands:
- "10:10 [alert]"
- "10:10 [log]"
- "20:20 [webhook]"
- "20:20 [proxy]"
Reach:
remove-violations-after: 300
checks:
- "Reach"
commands:
- "1:1 [alert]"
- "1:1 [log]"
- "1:1 [webhook]"
- "1:1 [proxy]"
Hitboxes:
@@ -72,8 +86,27 @@ Punishments:
- "Hitboxes"
commands:
- "5:3 [alert]"
- "5:3 [log]"
- "5:3 [webhook]"
- "5:3 [proxy]"
WallHit:
remove-violations-after: 300
checks:
- "WallHit"
commands:
- "10:5 [alert]"
- "10:5 [webhook]"
- "10:5 [proxy]"
- "10:5 [log]"
EntityPierce:
remove-violations-after: 300
checks:
- "EntityPierce"
commands:
- "15:10 [alert]"
- "15:10 [webhook]"
- "15:10 [proxy]"
- "15:10 [log]"
Misc:
remove-violations-after: 300
checks:
@@ -88,6 +121,7 @@ Punishments:
- "Elytra"
commands:
- "10:5 [alert]"
- "10:5 [log]"
- "20:10 [webhook]"
- "20:10 [proxy]"
Combat:
@@ -98,6 +132,7 @@ Punishments:
- "Aim"
commands:
- "20:40 [alert]"
- "20:40 [log]"
# Grim2.0.10版本 没有连点器检查,Grim将在未来添加
Autoclicker:
remove-violations-after: 300
@@ -105,3 +140,4 @@ Punishments:
- "Autoclicker"
commands:
- "20:40 [alert]"
- "20:40 [log]"
@@ -1,6 +1,6 @@
package ac.grim.grimac.platform.fabric.mc1161;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer;
import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser;
import ac.grim.grimac.platform.fabric.manager.FabricParserDescriptorFactory;
import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter;
@@ -32,7 +32,7 @@ public class GrimACFabric1161LoaderPlugin extends ac.grim.grimac.platform.fabric
}
protected GrimACFabric1161LoaderPlugin(FabricPlatformPlayerFactory playerFactory,
PlatformServer platformServer,
AbstractFabricPlatformServer platformServer,
IFabricMessageUtil fabricMessageUtil,
IFabricConversionUtil fabricConversionUtil) {
super(
@@ -0,0 +1,18 @@
package ac.grim.grimac.platform.fabric.mc1171;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer;
import com.mojang.authlib.GameProfile;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class Fabric1171PlatformServer extends Fabric1140PlatformServer {
@Override @Nullable
public GameProfile getProfileByName(String name) {
Optional<GameProfile> gameProfile = GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name);
return gameProfile.orElse(null);
}
}
@@ -1,6 +1,6 @@
package ac.grim.grimac.platform.fabric.mc1171;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer;
import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser;
@@ -15,6 +15,7 @@ import ac.grim.grimac.platform.fabric.mc1161.util.convert.Fabric1161MessageUtil;
import ac.grim.grimac.platform.fabric.player.FabricPlatformPlayerFactory;
import ac.grim.grimac.platform.fabric.utils.convert.IFabricConversionUtil;
import ac.grim.grimac.platform.fabric.utils.message.IFabricMessageUtil;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
public class GrimACFabric1170LoaderPlugin extends GrimACFabricLoaderPlugin {
@@ -28,7 +29,8 @@ public class GrimACFabric1170LoaderPlugin extends GrimACFabricLoaderPlugin {
Fabric1170GrimEntity::new,
Fabric1161PlatformInventory::new
),
new Fabric1140PlatformServer(),
PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_17)
? new Fabric1171PlatformServer() : new Fabric1140PlatformServer(),
new Fabric1161MessageUtil(),
new Fabric1140ConversionUtil()
);
@@ -36,7 +38,7 @@ public class GrimACFabric1170LoaderPlugin extends GrimACFabricLoaderPlugin {
protected GrimACFabric1170LoaderPlugin(ParserDescriptorFactory parserDescriptorFactory,
FabricPlatformPlayerFactory playerFactory,
PlatformServer platformServer,
AbstractFabricPlatformServer platformServer,
IFabricMessageUtil fabricMessageUtil,
IFabricConversionUtil fabricConversionUtil) {
super(
@@ -2,10 +2,10 @@ package ac.grim.grimac.platform.fabric.mc1194;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import ac.grim.grimac.platform.fabric.mc1161.Fabric1140PlatformServer;
import ac.grim.grimac.platform.fabric.mc1171.Fabric1171PlatformServer;
import net.minecraft.server.command.ServerCommandSource;
public class Fabric1190PlatformServer extends Fabric1140PlatformServer {
public class Fabric1190PlatformServer extends Fabric1171PlatformServer {
@Override
public void dispatchCommand(Sender sender, String command) {
ServerCommandSource commandSource = GrimACFabricLoaderPlugin.LOADER.getFabricSenderFactory().reverse(sender);
@@ -1,6 +1,6 @@
package ac.grim.grimac.platform.fabric.mc1194;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.fabric.AbstractFabricPlatformServer;
import ac.grim.grimac.platform.api.manager.ParserDescriptorFactory;
import ac.grim.grimac.platform.fabric.mc1161.command.Fabric1161PlayerSelectorAdapter;
import ac.grim.grimac.platform.fabric.command.FabricPlayerSelectorParser;
@@ -40,7 +40,7 @@ public class GrimACFabric1190LoaderPlugin extends GrimACFabric1170LoaderPlugin {
protected GrimACFabric1190LoaderPlugin(
ParserDescriptorFactory parserDescriptorFactory,
FabricPlatformPlayerFactory platformPlayerFactory,
PlatformServer platformServer,
AbstractFabricPlatformServer platformServer,
IFabricMessageUtil fabricMessageUtil,
IFabricConversionUtil fabricConversionUtil) {
super(parserDescriptorFactory, platformPlayerFactory, platformServer, fabricMessageUtil, fabricConversionUtil);
@@ -2,8 +2,10 @@ package ac.grim.grimac.platform.fabric;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.api.sender.Sender;
import com.mojang.authlib.GameProfile;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.command.ServerCommandSource;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractFabricPlatformServer implements PlatformServer {
@@ -24,4 +26,9 @@ public abstract class AbstractFabricPlatformServer implements PlatformServer {
public void registerOutgoingPluginChannel(String name) {
throw new UnsupportedOperationException();
}
@Nullable
public GameProfile getProfileByName(String name) {
return GrimACFabricLoaderPlugin.FABRIC_SERVER.getUserCache().findByName(name);
}
}
@@ -5,7 +5,6 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.GrimAPIProvider;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.platform.api.PlatformLoader;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.api.manager.*;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.api.sender.SenderFactory;
@@ -58,14 +57,14 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader {
protected final ParserDescriptorFactory parserFactory;
protected final FabricPlatformPlayerFactory playerFactory;
protected final PlatformServer platformServer;
protected final AbstractFabricPlatformServer platformServer;
protected final IFabricConversionUtil fabricConversionUtil;
protected final IFabricMessageUtil fabricMessageUtil;
public GrimACFabricLoaderPlugin(
ParserDescriptorFactory parserDescriptorFactory,
FabricPlatformPlayerFactory playerFactory,
PlatformServer platformServer,
AbstractFabricPlatformServer platformServer,
IFabricMessageUtil fabricMessageUtil,
IFabricConversionUtil fabricConversionUtil
) {
@@ -148,7 +147,7 @@ public abstract class GrimACFabricLoaderPlugin implements PlatformLoader {
}
@Override
public PlatformServer getPlatformServer() {
public AbstractFabricPlatformServer getPlatformServer() {
return platformServer;
}
@@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.server.network.ServerPlayerEntity;
// Works from 1.14 - latest (1.21.5)
@Mixin(ServerPlayerEntity.class)
abstract class ServerPlayerEntityMixin {
@@ -0,0 +1,40 @@
package ac.grim.grimac.platform.fabric.player;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class FabricOfflinePlatformPlayer implements OfflinePlatformPlayer {
private final UUID uuid;
private final String name;
public FabricOfflinePlatformPlayer(@NotNull UUID uuid, @NotNull String name) {
this.uuid = uuid;
this.name = name;
}
@Override
public boolean isOnline() {
return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid) != null;
}
@Override
public @NotNull String getName() {
return name;
}
@Override
public @NotNull UUID getUniqueId() {
return uuid;
}
@Override
public boolean equals(Object o) {
if (o instanceof OfflinePlatformPlayer offlinePlatformPlayer) {
return this.getUniqueId().equals(offlinePlatformPlayer.getUniqueId());
}
return false;
}
}
@@ -2,18 +2,24 @@ package ac.grim.grimac.platform.fabric.player;
import ac.grim.grimac.platform.api.entity.GrimEntity;
import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
public class FabricPlatformPlayerFactory extends AbstractPlatformPlayerFactory<ServerPlayerEntity> {
private final Map<UUID, OfflinePlatformPlayer> offlinePlatformPlayerCache = new HashMap<>();
private final Function<ServerPlayerEntity, AbstractFabricPlatformPlayer> getPlayerFunction;
private final Function<Entity, GrimEntity> getEntityFunction;
private final Function<ServerPlayerEntity, AbstractFabricPlatformInventory> getPlayerInventoryFunction;
@@ -32,6 +38,11 @@ public class FabricPlatformPlayerFactory extends AbstractPlatformPlayerFactory<S
return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(uuid);
}
@Override
protected ServerPlayerEntity getNativePlayer(@NonNull String name) {
return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayer(name);
}
@Override
protected AbstractFabricPlatformPlayer createPlatformPlayer(@NotNull ServerPlayerEntity nativePlayer) {
return getPlayerFunction.apply(nativePlayer);
@@ -58,6 +69,43 @@ public class FabricPlatformPlayerFactory extends AbstractPlatformPlayerFactory<S
return GrimACFabricLoaderPlugin.FABRIC_SERVER.getPlayerManager().getPlayerList();
}
@Override
public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) {
return null;
}
@Override
public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) {
OfflinePlatformPlayer result = this.getFromName(name);
if (result == null) {
GameProfile profile = null;
// Only fetch an online UUID in online mode
// TODO (cross-platform) add a config option for "offline-mode" servers with online-mode behind a proxy
if (GrimACFabricLoaderPlugin.FABRIC_SERVER.isOnlineMode()) {
// THIS CAN BLOCK THE CALLING THREAD!
profile = GrimACFabricLoaderPlugin.LOADER.getPlatformServer().getProfileByName(name);
}
if (profile == null) {
// Make an OfflinePlayer using an offline mode UUID since the name has no profile
result = this.getOfflinePlayer(new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)), name));
} else {
// Use the GameProfile even when we get a UUID so we ensure we still have a name
result = this.getOfflinePlayer(profile);
}
} else {
this.offlinePlatformPlayerCache.remove(result.getUniqueId());
}
return result;
}
public OfflinePlatformPlayer getOfflinePlayer(GameProfile profile) {
OfflinePlatformPlayer player = new FabricOfflinePlatformPlayer(profile.getId(), profile.getName());
this.offlinePlatformPlayerCache.put(profile.getId(), player);
return player;
}
@Override
public void replaceNativePlayer(@NonNull UUID uuid, @NonNull ServerPlayerEntity serverPlayerEntity) {
super.cache.getPlayer(uuid).replaceNativePlayer(serverPlayerEntity);