Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b12083427 | ||
|
|
bc6d5e3b8f | ||
|
|
7a3e932779 | ||
|
|
6eb358e28e | ||
|
|
efbc365b67 | ||
|
|
36ae27a79a |
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()
|
||||
}
|
||||
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
+131
@@ -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
|
||||
}
|
||||
}
|
||||
+17
@@ -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(); }
|
||||
}
|
||||
+130
@@ -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();
|
||||
|
||||
}
|
||||
+101
@@ -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.
|
||||
*
|
||||
|
||||
+24
-3
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
+26
-15
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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]"
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -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
-2
@@ -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);
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user