Merge remote-tracking branch 'origin/2.0' into lightning

# Conflicts:
#	common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java
This commit is contained in:
Axionize committed 2026-04-15 10:17:13 -04:00
commit 65815fc73b
54 files changed
+660 -437

No files matched your search

+11 -1
View File
@@ -233,7 +233,7 @@ jobs:
dependencies: 'fabric-api'
- name: Publish to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' }}
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' && inputs.is-release-build == false }}
run: |
curl -X POST \
-H "Content-Type: application/json" \
@@ -241,3 +241,13 @@ jobs:
-H "Authorization: Bearer ${{ secrets.BUILD_UPDATE_TOKEN }}" \
-d '{"org": "${{ github.repository_owner }}", "repository": "${{ github.event.repository.name }}", "branch": "${{ github.ref_name }}", "version": "${{ env.BUILD_VERSION }}"}' \
"${{ secrets.BUILD_UPDATE_URL }}"
- name: Publish Release to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' && inputs.is-release-build == true }}
run: |
curl -X POST \
-H "Content-Type: application/json" \
-H "User-Agent: GrimACBuilder/v1" \
-H "Authorization: Bearer ${{ secrets.BUILD_UPDATE_TOKEN }}" \
-d '{"org": "${{ github.repository_owner }}", "repository": "${{ github.event.repository.name }}", "branch": "release", "version": "${{ env.BUILD_VERSION }}"}' \
"${{ secrets.BUILD_UPDATE_URL }}"
@@ -176,7 +176,8 @@ public class Check extends GrimProcessor implements AbstractCheck {
public static boolean isAsync(PacketTypeCommon packetType) {
return packetType == PacketType.Play.Client.KEEP_ALIVE
|| packetType == PacketType.Play.Client.CHUNK_BATCH_ACK;
|| packetType == PacketType.Play.Client.CHUNK_BATCH_ACK
|| packetType == PacketType.Play.Client.RESOURCE_PACK_STATUS;
}
public boolean isUpdate(PacketTypeCommon packetType) {
@@ -0,0 +1,26 @@
package ac.grim.grimac.checks.impl.badpackets;
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;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
import org.jetbrains.annotations.NotNull;
@CheckData(name = "BadPacketsC", description = "Tried to wake up while not sleeping", experimental = true)
public class BadPacketsC extends Check implements PacketCheck {
public BadPacketsC(@NotNull GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.LEAVE_BED
&& !player.isInBed) {
flagAndAlert();
}
}
}
@@ -10,6 +10,7 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
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.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsT")
@@ -35,37 +36,39 @@ public class BadPacketsT extends Check implements PacketCheck {
if (event.getPacketType().equals(PacketType.Play.Client.INTERACT_ENTITY)) {
final WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
// Only INTERACT_AT actually has an interaction vector
wrapper.getTarget().ifPresent(targetVector -> {
final PacketEntity packetEntity = player.compensatedEntities.getEntity(wrapper.getEntityId());
// Don't continue if the compensated entity hasn't been resolved
if (packetEntity == null) {
return;
}
if (wrapper.getAction() != WrapperPlayClientInteractEntity.InteractAction.INTERACT_AT) return;
Vector3d targetVector = wrapper.getLocation();
if (targetVector == null) return; // shouldn't ever happen, but whatever
// Make sure our target entity is actually a player (Player NPCs work too)
if (!EntityTypes.PLAYER.equals(packetEntity.type)) {
// We can't check for any entity that is not a player
return;
}
final PacketEntity packetEntity = player.compensatedEntities.getEntity(wrapper.getEntityId());
// Don't continue if the compensated entity hasn't been resolved
if (packetEntity == null) {
return;
}
// Perform the interaction vector check
// TODO:
// 27/12/2023 - Dynamic values for more than just one entity type?
// 28/12/2023 - Player-only is fine
// 30/12/2023 - Expansions differ in 1.9+
final float scale = (float) packetEntity.getAttributeValue(Attributes.SCALE);
if (targetVector.y > (minVerticalDisplacement * scale) && targetVector.y < (maxVerticalDisplacement * scale)
&& Math.abs(targetVector.x) < (maxHorizontalDisplacement * scale)
&& Math.abs(targetVector.z) < (maxHorizontalDisplacement * scale)) {
return;
}
// Make sure our target entity is actually a player (Player NPCs work too)
if (!EntityTypes.PLAYER.equals(packetEntity.type)) {
// We can't check for any entity that is not a player
return;
}
// Log the vector
final String verbose = String.format("%.5f/%.5f/%.5f",
targetVector.x, targetVector.y, targetVector.z);
// We could pretty much ban the player at this point
flagAndAlert(verbose);
});
// Perform the interaction vector check
// TODO:
// 27/12/2023 - Dynamic values for more than just one entity type?
// 28/12/2023 - Player-only is fine
// 30/12/2023 - Expansions differ in 1.9+
final float scale = (float) packetEntity.getAttributeValue(Attributes.SCALE);
if (targetVector.y > (minVerticalDisplacement * scale) && targetVector.y < (maxVerticalDisplacement * scale)
&& Math.abs(targetVector.x) < (maxHorizontalDisplacement * scale)
&& Math.abs(targetVector.z) < (maxHorizontalDisplacement * scale)) {
return;
}
// Log the vector
final String verbose = String.format("%.5f/%.5f/%.5f",
targetVector.x, targetVector.y, targetVector.z);
// We could pretty much ban the player at this point
flagAndAlert(verbose);
}
}
}
@@ -7,7 +7,9 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAttack;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientSpectateEntity;
import java.util.ArrayList;
@@ -28,23 +30,17 @@ public class MultiInteractA extends Check implements PostPredictionCheck {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
int entity = packet.getEntityId();
boolean sneaking = packet.isSneaking().orElse(false);
onInteract(event, entity, sneaking);
}
if (hasInteracted && entity != lastEntity) {
String verbose = "lastEntity=" + lastEntity + ", entity=" + entity
+ ", lastSneaking=" + lastSneaking + ", sneaking=" + sneaking;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK) {
WrapperPlayClientAttack packet = new WrapperPlayClientAttack(event);
onInteract(event, packet.getEntityId(), lastSneaking);
}
lastEntity = entity;
lastSneaking = sneaking;
hasInteracted = true;
if (event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
WrapperPlayClientSpectateEntity packet = new WrapperPlayClientSpectateEntity(event);
onInteract(event, packet.getEntityId(), lastSneaking);
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
@@ -52,6 +48,25 @@ public class MultiInteractA extends Check implements PostPredictionCheck {
}
}
private void onInteract(PacketReceiveEvent event, int entity, boolean sneaking) {
if (hasInteracted && (entity != lastEntity || sneaking != lastSneaking)) {
String verbose = "lastEntity=" + lastEntity + ", entity=" + entity
+ ", lastSneaking=" + lastSneaking + ", sneaking=" + sneaking;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
lastEntity = entity;
lastSneaking = sneaking;
hasInteracted = true;
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
@@ -8,7 +8,7 @@ import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import java.util.ArrayList;
@@ -16,8 +16,8 @@ import java.util.ArrayList;
@CheckData(name = "MultiInteractB", experimental = true)
public class MultiInteractB extends Check implements PostPredictionCheck {
private final ArrayList<String> flags = new ArrayList<>();
private Vector3f lastPos;
private boolean hasInteracted = false;
private Vector3d lastPos;
private boolean hasInteracted;
public MultiInteractB(final GrimPlayer player) {
super(player);
@@ -26,11 +26,11 @@ public class MultiInteractB extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
Vector3f pos = new WrapperPlayClientInteractEntity(event).getTarget().orElse(null);
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
if (packet.getAction() != WrapperPlayClientInteractEntity.InteractAction.INTERACT_AT) return;
if (pos == null) {
return;
}
Vector3d pos = packet.getLocation();
if (pos == null) return; // shouldn't ever happen, but whatever
if (hasInteracted && !pos.equals(lastPos)) {
String verbose = "pos=" + MessageUtil.toUnlabledString(pos) + ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
@@ -50,6 +50,7 @@ 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.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAttack;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@@ -90,114 +91,14 @@ public class Reach extends Check implements PacketCheck {
@Override
public void onPacketReceive(final PacketReceiveEvent event) {
if (!player.disableGrim && event.getPacketType() == PacketType.Play.Client.ATTACK) {
WrapperPlayClientAttack packet = new WrapperPlayClientAttack(event);
onInteract(event, packet.getEntityId(), InteractionHand.MAIN_HAND);
}
if (!player.disableGrim && event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity action = new WrapperPlayClientInteractEntity(event);
// Don't let the player teleport to bypass reach
if (player.getSetbackTeleportUtil().shouldBlockMovement()) {
event.setCancelled(true);
player.onPacketCancel();
return;
}
PacketEntity entity = player.compensatedEntities.entityMap.get(action.getEntityId());
// Stop people from freezing transactions before an entity spawns to bypass reach
// TODO: implement dragon parts?
if (entity == null || entity instanceof PacketEntityEnderDragonPart) {
// Only cancel if and only if we are tracking this entity
// This is because we don't track paintings.
if (shouldModifyPackets() && player.compensatedEntities.serverPositionsMap.containsKey(action.getEntityId())) {
event.setCancelled(true);
player.onPacketCancel();
}
return;
}
if (ignoreNonPlayerTargets && !entity.type.equals(EntityTypes.PLAYER)) {
return;
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
// TODO: Remove when in front of via
if (entity.type == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8))
return;
//Prevents Happy Ghast Reach false on 1.21.6+ servers with ViaBackwards set up
if (entity.type == EntityTypes.HAPPY_GHAST && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_6)) {
return;
}
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR)
return;
if (player.inVehicle()) return;
if (entity.riding != null) return;
InteractionHand hand = action.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK ?
InteractionHand.MAIN_HAND : action.getHand(); // attacks can be only performed with the main hand
ItemStack currentStack = player.inventory.getItemInHand(hand);
ItemStack startStack = player.inventory.getStartOfTickStack();
boolean hasRange = false;
float maxReach = 0f;
float hitboxMargin = 0f;
boolean clientAttackRangeExists = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11);
boolean clientAndServerAgrees = clientAttackRangeExists && ATTACK_RANGE_COMPONENT_EXISTS;
boolean viaVersionAvailable = false;
if (USE_1_8_HITBOX_MARGIN && ViaVersionUtil.isAvailable) {
viaVersionAvailable = Via.getConfig().getValues().containsKey("use-1_8-hitbox-margin") && Via.getConfig().use1_8HitboxMargin();
}
boolean clientAndViaVersion = clientAttackRangeExists && viaVersionAvailable;
if (clientAndServerAgrees || clientAndViaVersion) {
ItemAttackRange startRange = startStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
ItemAttackRange currentRange = currentStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
if (clientAndViaVersion) {
if (startStack != ItemStack.EMPTY) {
startRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
}
if (currentStack != ItemStack.EMPTY) {
currentRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
}
}
// If the start stack has no range component, the client defaults to vanilla reach behavior,
// regardless of what the current stack is (No Range -> X = No Range used).
if (startRange != null) {
if (currentRange == null) {
// Range (Start) -> No Range (Current)
// Client logic uses Start Range
hasRange = true;
maxReach = startRange.getMaxRange();
hitboxMargin = startRange.getHitboxMargin();
} else {
// Range (Start) -> Range (Current)
// Client logic requires satisfying BOTH constraints
hasRange = true;
maxReach = Math.min(startRange.getMaxRange(), currentRange.getMaxRange());
hitboxMargin = Math.min(startRange.getHitboxMargin(), currentRange.getHitboxMargin());
}
}
}
boolean tooManyAttacks = playerAttackQueue.size() > 10;
if (!tooManyAttacks) {
playerAttackQueue.put(action.getEntityId(), new InteractionData(
player.x, player.y, player.z,
hasRange, maxReach, hitboxMargin
)); // Queue for next tick for very precise check
}
boolean knownInvalid = isKnownInvalid(entity, hasRange, maxReach, hitboxMargin);
if ((shouldModifyPackets() && cancelImpossibleHits && knownInvalid) || tooManyAttacks) {
event.setCancelled(true);
player.onPacketCancel();
}
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
onInteract(event, packet.getEntityId(), packet.getHand());
}
// If the player set their look, or we know they have a new tick
@@ -207,6 +108,109 @@ public class Reach extends Check implements PacketCheck {
}
}
private void onInteract(PacketReceiveEvent event, int entityId, InteractionHand hand) {
// Don't let the player teleport to bypass reach
if (player.getSetbackTeleportUtil().shouldBlockMovement()) {
event.setCancelled(true);
player.onPacketCancel();
return;
}
PacketEntity entity = player.compensatedEntities.entityMap.get(entityId);
// Stop people from freezing transactions before an entity spawns to bypass reach
// TODO: implement dragon parts?
if (entity == null || entity instanceof PacketEntityEnderDragonPart) {
// Only cancel if and only if we are tracking this entity
// This is because we don't track paintings.
if (shouldModifyPackets() && player.compensatedEntities.serverPositionsMap.containsKey(entityId)) {
event.setCancelled(true);
player.onPacketCancel();
}
return;
}
if (ignoreNonPlayerTargets && !entity.type.equals(EntityTypes.PLAYER)) {
return;
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
// TODO: Remove when in front of via
if (entity.type == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8))
return;
// Prevents Happy Ghast Reach false on 1.21.6+ servers with ViaBackwards set up
if (entity.type == EntityTypes.HAPPY_GHAST && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_6))
return;
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR)
return;
if (player.inVehicle()) return;
if (entity.riding != null) return;
ItemStack currentStack = player.inventory.getItemInHand(hand);
ItemStack startStack = player.inventory.getStartOfTickStack();
boolean hasRange = false;
float maxReach = 0f;
float hitboxMargin = 0f;
boolean clientAttackRangeExists = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11);
boolean clientAndServerAgrees = clientAttackRangeExists && ATTACK_RANGE_COMPONENT_EXISTS;
boolean viaVersionAvailable = false;
if (USE_1_8_HITBOX_MARGIN && ViaVersionUtil.isAvailable) {
viaVersionAvailable = Via.getConfig().getValues().containsKey("use-1_8-hitbox-margin") && Via.getConfig().use1_8HitboxMargin();
}
boolean clientAndViaVersion = clientAttackRangeExists && viaVersionAvailable;
if (clientAndServerAgrees || clientAndViaVersion) {
ItemAttackRange startRange = startStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
ItemAttackRange currentRange = currentStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
if (clientAndViaVersion) {
if (startStack != ItemStack.EMPTY) {
startRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
}
if (currentStack != ItemStack.EMPTY) {
currentRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
}
}
// If the start stack has no range component, the client defaults to vanilla reach behavior,
// regardless of what the current stack is (No Range -> X = No Range used).
if (startRange != null) {
hasRange = true;
if (currentRange == null) {
// Range (Start) -> No Range (Current)
// Client logic uses Start Range
maxReach = startRange.getMaxRange();
hitboxMargin = startRange.getHitboxMargin();
} else {
// Range (Start) -> Range (Current)
// Client logic requires satisfying BOTH constraints
maxReach = Math.min(startRange.getMaxRange(), currentRange.getMaxRange());
hitboxMargin = Math.min(startRange.getHitboxMargin(), currentRange.getHitboxMargin());
}
}
}
boolean tooManyAttacks = playerAttackQueue.size() > 10;
if (!tooManyAttacks) {
playerAttackQueue.put(entityId, new InteractionData(
player.x, player.y, player.z,
hasRange, maxReach, hitboxMargin
)); // Queue for next tick for very precise check
}
boolean knownInvalid = isKnownInvalid(entity, hasRange, maxReach, hitboxMargin);
if ((shouldModifyPackets() && cancelImpossibleHits && knownInvalid) || tooManyAttacks) {
event.setCancelled(true);
player.onPacketCancel();
}
}
// This method finds the most optimal point at which the user should be aiming at
// and then measures the distance between the player's eyes and this target point
//
@@ -6,7 +6,9 @@ import ac.grim.grimac.checks.type.PacketCheck;
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.WrapperPlayClientAttack;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientSpectateEntity;
@CheckData(name = "SelfInteract", description = "Interacted with self")
public class SelfInteract extends Check implements PacketCheck {
@@ -16,11 +18,26 @@ public class SelfInteract extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
&& player.cameraEntity.isSelf() // TODO: should check for camera entity id?
&& new WrapperPlayClientInteractEntity(event).getEntityId() == player.entityID
&& flagAndAlert() && shouldModifyPackets() // Instant ban
) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
onInteract(event, packet.getEntityId());
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK) {
WrapperPlayClientAttack packet = new WrapperPlayClientAttack(event);
onInteract(event, packet.getEntityId());
}
if (event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
WrapperPlayClientSpectateEntity packet = new WrapperPlayClientSpectateEntity(event);
onInteract(event, packet.getEntityId());
}
}
// TODO: should check for camera entity id instead of player entity id?
private void onInteract(PacketReceiveEvent event, int entityId) {
if (player.cameraEntity.isSelf() && entityId == player.entityID
&& flagAndAlert() && shouldModifyPackets()) { // Instant ban
event.setCancelled(true);
player.onPacketCancel();
}
@@ -6,9 +6,7 @@ import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraA", description = "Started gliding while already gliding")
public class ElytraA extends Check implements PostPredictionCheck {
@@ -33,13 +31,6 @@ public class ElytraA extends Check implements PostPredictionCheck {
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_15) && event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
) onStartGliding(event);
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
@@ -21,15 +21,7 @@ import java.util.ArrayDeque;
import java.util.List;
import java.util.Locale;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.ANIMATION;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.CLICK_WINDOW;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.ENTITY_ACTION;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.HELD_ITEM_CHANGE;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.INTERACT_ENTITY;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_ABILITIES;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_DIGGING;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.USE_ITEM;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.*;
@CheckData(name = "Post")
public class Post extends Check implements PacketCheck, PostPredictionCheck {
@@ -90,6 +82,7 @@ public class Post extends Check implements PacketCheck, PostPredictionCheck {
} else if (PLAYER_ABILITIES.equals(packetType)
|| (HELD_ITEM_CHANGE.equals(packetType) && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8))
|| INTERACT_ENTITY.equals(packetType) || PLAYER_BLOCK_PLACEMENT.equals(packetType)
|| ATTACK.equals(packetType) || SPECTATE_ENTITY.equals(packetType)
|| USE_ITEM.equals(packetType) || PLAYER_DIGGING.equals(packetType)) {
if (sentFlying) post.add(event.getPacketType());
} else if (CLICK_WINDOW.equals(packetType) && player.getClientVersion().isOlderThan(ClientVersion.V_1_13)) {
@@ -107,6 +100,7 @@ public class Post extends Check implements PacketCheck, PostPredictionCheck {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19_3) && player.inVehicle()) {
return;
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.LEAVE_BED) return;
if (sentFlying) post.add(event.getPacketType());
}
}
@@ -17,7 +17,9 @@ public class SetbackBlocker extends Check implements PacketCheck {
if (player.disableGrim)
return; // Let's avoid letting people disable grim with grim.nomodifypackets
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
|| event.getPacketType() == PacketType.Play.Client.ATTACK
|| event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
if (player.getSetbackTeleportUtil().cheatVehicleInterpolationDelay > 0) {
event.setCancelled(true); // Player is in the vehicle
}
@@ -6,8 +6,10 @@ import ac.grim.grimac.checks.type.PacketCheck;
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.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "MultiActionsA", description = "Attacked while using an item", experimental = true)
public class MultiActionsA extends Check implements PacketCheck {
@@ -17,8 +19,10 @@ public class MultiActionsA extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.itemInUseHand == InteractionHand.OFF_HAND) && event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.itemInUseHand == InteractionHand.OFF_HAND)) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK
|| event.getPacketType() == PacketType.Play.Client.ATTACK || event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY
|| event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING && new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.STAB) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -39,12 +39,15 @@ public class MultiActionsC extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW && !player.serverOpenedInventoryThisTick) {
String verbose = getVerbose(player);
if (!verbose.isEmpty() && flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (event.getPacketType() != PacketType.Play.Client.CLICK_WINDOW) return;
if (player.serverOpenedInventoryThisTick) return;
String verbose = getVerbose(player);
if (verbose.isEmpty()) return;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
@@ -15,12 +15,15 @@ public class MultiActionsD extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
String verbose = MultiActionsC.getVerbose(player);
if (!verbose.isEmpty() && flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
if (event.getPacketType() != PacketType.Play.Client.CLOSE_WINDOW) return;
if (player.serverOpenedInventoryThisTick) return;
String verbose = MultiActionsC.getVerbose(player);
if (verbose.isEmpty()) return;
// Don't cancel this packet, because it won't do anything except for making chests
// look like they are still open (desynced),
// and it can cause incompatibility issues with plugins
flagAndAlert(verbose);
}
}
@@ -38,7 +38,9 @@ public class MultiActionsF extends BlockPlaceCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
|| event.getPacketType() == PacketType.Play.Client.ATTACK
|| event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
entity = true;
if (block) {
if (!player.canSkipTicks()) {
@@ -24,6 +24,18 @@ public class MultiActionsG extends BlockPlaceCheck {
player.onPacketCancel();
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK && isCheckActive()
&& flagAndAlert("attack") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY && isCheckActive()
&& flagAndAlert("spectateEntity") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM && isCheckActive()
&& flagAndAlert("use") && shouldModifyPackets()) {
event.setCancelled(true);
@@ -9,10 +9,12 @@ 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;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAnimation;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "PacketOrderB", description = "Did not swing for attack")
public class PacketOrderB extends Check implements PacketCheck {
@@ -51,20 +53,23 @@ public class PacketOrderB extends Check implements PacketCheck {
return;
}
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && (player.gamemode != GameMode.SPECTATOR || player.getClientVersion().isOlderThan(ClientVersion.V_1_21_11))) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
if (packet.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
sentAttack = true;
onAttack(event);
return;
}
}
if (is1_9 ? !sentAnimationSinceLastAttack : !sentAnimation) {
sentAttack = false; // don't flag twice
if (flagAndAlert("pre-attack") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK) {
onAttack(event);
return;
}
sentAnimationSinceLastAttack = sentAnimation = sentSlotSwitch = false;
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
if (packet.getAction() == DiggingAction.STAB) {
onAttack(event);
return;
}
}
@@ -82,4 +87,20 @@ public class PacketOrderB extends Check implements PacketCheck {
sentAttack = sentAnimation = sentSlotSwitch = false;
}
}
private void onAttack(PacketReceiveEvent event) {
if (player.gamemode == GameMode.SPECTATOR && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11)) return;
sentAttack = true;
if (is1_9 ? !sentAnimationSinceLastAttack : !sentAnimation) {
sentAttack = false; // don't flag twice
if (flagAndAlert("pre-attack") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentAnimationSinceLastAttack = sentAnimation = sentSlotSwitch = false;
}
}
@@ -5,7 +5,9 @@ import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
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.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@@ -15,8 +17,9 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPl
@CheckData(name = "PacketOrderC")
public class PacketOrderC extends Check implements PacketCheck {
// 1.7 players do not send INTERACT_AT, so we cannot check them
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10);
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10) // 1.7 players do not send INTERACT_AT
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_26_1) // 26.1 players do not send INTERACT
|| PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_26_1); // ViaBackwards drops INTERACT on 26.1 servers
private boolean sentInteractAt = false;
private int requiredEntity;
private InteractionHand requiredHand;
@@ -30,8 +30,11 @@ public class PacketOrderD extends Check implements PacketCheck {
final boolean sneaking = packet.isSneaking().orElse(false);
final int entity = packet.getEntityId();
// via inserts these wrong...
if (action == InteractAction.INTERACT && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_26_1)) return;
if (packet.getHand() == InteractionHand.OFF_HAND) {
if (action == InteractAction.INTERACT) {
if (action == InteractAction.INTERACT || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_26_1)) {
if (!sentMainhand) {
if (flagAndAlert("Skipped Mainhand") && shouldModifyPackets()) {
event.setCancelled(true);
@@ -39,12 +42,16 @@ public class PacketOrderD extends Check implements PacketCheck {
}
}
sentMainhand = false;
} else if (sneaking != requiredSneaking || entity != requiredEntity) {
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (action == InteractAction.INTERACT_AT) {
if (sneaking != requiredSneaking || entity != requiredEntity) {
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
} else {
@@ -22,7 +22,7 @@ public class PacketOrderE extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
if (player.packetOrderProcessor.isAttacking()
if (player.packetOrderProcessor.isAttackingOrStabbing()
|| player.packetOrderProcessor.isRightClicking()
|| player.packetOrderProcessor.isOpeningInventory()
|| player.packetOrderProcessor.isReleasing()
@@ -32,7 +32,7 @@ public class PacketOrderE extends Check implements PostPredictionCheck {
|| player.packetOrderProcessor.isStartingToGlide()
|| player.packetOrderProcessor.isJumpingWithMount()
) {
String verbose = "attacking=" + player.packetOrderProcessor.isAttacking()
String verbose = "attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", openingInventory=" + player.packetOrderProcessor.isOpeningInventory()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
@@ -23,6 +23,8 @@ public class PacketOrderF extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
|| event.getPacketType() == PacketType.Play.Client.ATTACK
|| event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY
|| event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT
|| event.getPacketType() == PacketType.Play.Client.USE_ITEM
|| event.getPacketType() == PacketType.Play.Client.PICK_ITEM
@@ -31,6 +33,8 @@ public class PacketOrderF extends Check implements PostPredictionCheck {
&& new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.OPEN_INVENTORY_ACHIEVEMENT)
) if (player.packetOrderProcessor.isSprinting() || player.packetOrderProcessor.isSneaking()) {
String verbose = "action=" + (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY ? "interact"
: event.getPacketType() == PacketType.Play.Client.ATTACK ? "attack"
: event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY ? "spectateEntity"
: event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT ? "place"
: event.getPacketType() == PacketType.Play.Client.USE_ITEM ? "use"
: event.getPacketType() == PacketType.Play.Client.PICK_ITEM ? "pick"
@@ -28,21 +28,20 @@ public class PacketOrderG extends Check implements PostPredictionCheck {
DiggingAction action = null;
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
action = new WrapperPlayClientPlayerDigging(event).getAction();
if (action == DiggingAction.RELEASE_USE_ITEM
|| action == DiggingAction.START_DIGGING
|| action == DiggingAction.CANCELLED_DIGGING
|| action == DiggingAction.FINISHED_DIGGING
if (action != DiggingAction.SWAP_ITEM_WITH_OFFHAND
&& action != DiggingAction.DROP_ITEM
&& action != DiggingAction.DROP_ITEM_STACK
) return;
}
if (player.packetOrderProcessor.isAttacking()
if (player.packetOrderProcessor.isAttackingOrStabbing()
|| player.packetOrderProcessor.isReleasing()
|| player.packetOrderProcessor.isRightClicking()
|| player.packetOrderProcessor.isPicking()
|| player.packetOrderProcessor.isDigging()
) {
String verbose = "action=" + (action == null ? "openInventory" : action == DiggingAction.SWAP_ITEM_WITH_OFFHAND ? "swap" : "drop")
+ ", attacking=" + player.packetOrderProcessor.isAttacking()
+ ", attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
@@ -32,20 +32,7 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=attack, rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
onAttack(event);
} else if (player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=interact, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
@@ -73,13 +60,20 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
}
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK || event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
onAttack(event);
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
switch (packet.getAction()) {
case STAB:
onAttack(event);
break;
case RELEASE_USE_ITEM:
if (player.packetOrderProcessor.isAttacking() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttacking()
if (player.packetOrderProcessor.isAttackingOrStabbing() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", digging=" + player.packetOrderProcessor.isDigging();
@@ -134,6 +128,23 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
setback = false;
}
private void onAttack(PacketReceiveEvent event) {
if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=attack" + ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
}
@Override
public void onReload(ConfigManager config) {
exemptPlacingWhileDigging = config.getBooleanElse(getConfigName() + ".exempt-placing-while-digging", false);
@@ -19,6 +19,7 @@ public class PacketOrderJ extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT || event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
// we don't check stabbing here because you don't need to target an entity to stab
if (player.packetOrderProcessor.isAttacking() && !player.packetOrderProcessor.isInteracting()) {
if (!player.canSkipTicks()) {
if (flagAndAlert() && shouldModifyPackets()) {
@@ -38,6 +38,7 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
private boolean leavingBed;
private boolean startingToGlide;
private boolean jumpingWithMount;
private boolean stabbing;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
@@ -57,12 +58,17 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
}
}
if (packetType == PacketType.Play.Client.ATTACK || packetType == PacketType.Play.Client.SPECTATE_ENTITY) {
attacking = true;
}
if (packetType == PacketType.Play.Client.PLAYER_DIGGING) {
switch (new WrapperPlayClientPlayerDigging(event).getAction()) {
case SWAP_ITEM_WITH_OFFHAND -> swapping = true;
case DROP_ITEM, DROP_ITEM_STACK -> dropping = true;
case RELEASE_USE_ITEM -> releasing = true;
case FINISHED_DIGGING, CANCELLED_DIGGING, START_DIGGING -> digging = true;
case STAB -> stabbing = true;
}
}
@@ -132,6 +138,7 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
leavingBed = false;
startingToGlide = false;
jumpingWithMount = false;
stabbing = false;
}
}
@@ -139,4 +146,9 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
public boolean isRightClicking() {
return placing || using || interacting;
}
@Contract(pure = true)
public boolean isAttackingOrStabbing() {
return attacking || stabbing;
}
}
@@ -24,6 +24,7 @@ import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.item.type.ItemType;
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
@@ -576,7 +577,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.packetStateData.cancelDuplicatePacket = false;
}
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END && player.supportsEndTick()) {
player.serverOpenedInventoryThisTick = false;
if (!player.packetStateData.didSendMovementBeforeTickEnd) {
// The player didn't send a movement packet, so we can predict this like we had idle tick on 1.8
@@ -602,11 +603,12 @@ public class CheckManagerListener extends PacketListenerAbstract {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
final PacketTypeCommon packetType = event.getPacketType();
if (packetType == PacketType.Play.Server.OPEN_WINDOW || packetType == PacketType.Play.Server.OPEN_HORSE_WINDOW) {
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.serverOpenedInventoryThisTick = true);
}
if (event.getPacketType() == PacketType.Play.Server.BUNDLE) {
if (packetType == PacketType.Play.Server.BUNDLE) {
player.packetStateData.sendingBundlePacket = !player.packetStateData.sendingBundlePacket;
}
@@ -627,7 +629,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
//
// removed a large rant, but I'm keeping this out of context insult below
// EVEN A BUNCH OF MONKEYS ON A TYPEWRITER COULDNT WRITE WORSE NETCODE THAN MOJANG
if (!player.packetStateData.lastPacketWasTeleport && flying.hasPositionChanged() && flying.hasRotationChanged() &&
if (flying.hasPositionChanged() && flying.hasRotationChanged() &&
// Ground status will never change in this stupidity packet
((flying.isOnGround() == player.packetStateData.packetPlayerOnGround
// Mojang added this stupid mechanic in 1.17
@@ -62,9 +62,11 @@ public class PacketEntityAction extends PacketListenerAbstract {
player.onPacketCancel();
break;
}
player.checkManager.getPostPredictionCheck(ElytraA.class).onStartGliding(event);
// Starting fall flying is server sided on 1.14 and below
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_15)) return;
player.checkManager.getPostPredictionCheck(ElytraA.class).onStartGliding(event);
// This shouldn't be needed with latency compensated inventories
// TODO: Remove this?
@@ -26,7 +26,7 @@ public class PacketHidePlayerInfo extends PacketListenerAbstract {
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.PLAYER_INFO) {
//iterate through players and fake their game mode if they are spectating via grim spectate
// iterate through players and fake their game mode if they are spectating via grim spectate
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_12_2))
return;
@@ -50,7 +50,7 @@ public class PacketHidePlayerInfo extends PacketListenerAbstract {
}
}
//if amount of hidden players is the amount of players updated & is an update game mode action just cancel it
// if amount of hidden players is the amount of players updated & is an update game mode action just cancel it
if (hideCount == nmsPlayerInfoDataList.size() && info.getAction() == WrapperPlayServerPlayerInfo.Action.UPDATE_GAME_MODE) {
event.setCancelled(true);
} else if (hideCount > 0) {
@@ -60,55 +60,55 @@ public class PacketHidePlayerInfo extends PacketListenerAbstract {
} else if (event.getPacketType() == PacketType.Play.Server.PLAYER_INFO_UPDATE) {
GrimPlayer receiver = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (receiver == null) return;
//create wrappers
WrapperPlayServerPlayerInfoUpdate wrapper = new WrapperPlayServerPlayerInfoUpdate(event);
EnumSet<WrapperPlayServerPlayerInfoUpdate.Action> actions = wrapper.getActions();
//player's game mode updated
if (actions.contains(WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_GAME_MODE)) {
boolean onlyGameMode = actions.size() == 1; // packet is being sent to only update game modes
int hideCount = 0;
List<WrapperPlayServerPlayerInfoUpdate.PlayerInfo> modified = new ArrayList<>(wrapper.getEntries().size());
//iterate through the player entries
for (WrapperPlayServerPlayerInfoUpdate.PlayerInfo entry : wrapper.getEntries()) {
//check if the player should be hidden
WrapperPlayServerPlayerInfoUpdate.PlayerInfo modifiedPacket = null;
final UserProfile gameProfile = entry.getGameProfile();
if (GrimAPI.INSTANCE.getSpectateManager().shouldHidePlayer(receiver, gameProfile.getUUID())) {
hideCount++;
//modify & create a new packet from pre-existing one if they are a spectator
if (entry.getGameMode() == GameMode.SPECTATOR) {
modifiedPacket = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo(
gameProfile,
entry.isListed(),
entry.getLatency(),
GameMode.SURVIVAL,
entry.getDisplayName(),
entry.getChatSession()
);
modified.add(modifiedPacket);
}
}
if (!actions.contains(WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_GAME_MODE)) return;
if (modifiedPacket == null) { //if the packet wasn't modified, send original
modified.add(entry);
} else if (!onlyGameMode) { //if more than just the game mode updated, modify the packet
boolean onlyGameMode = actions.size() == 1; // packet is being sent to only update game modes
int hideCount = 0;
List<WrapperPlayServerPlayerInfoUpdate.PlayerInfo> modified = new ArrayList<>(wrapper.getEntries().size());
// iterate through the player entries
for (WrapperPlayServerPlayerInfoUpdate.PlayerInfo entry : wrapper.getEntries()) {
// check if the player should be hidden
WrapperPlayServerPlayerInfoUpdate.PlayerInfo modifiedPacket = null;
final UserProfile gameProfile = entry.getGameProfile();
if (GrimAPI.INSTANCE.getSpectateManager().shouldHidePlayer(receiver, gameProfile.getUUID())) {
hideCount++;
// modify & create a new packet from pre-existing one if they are a spectator
if (entry.getGameMode() == GameMode.SPECTATOR) {
modifiedPacket = new WrapperPlayServerPlayerInfoUpdate.PlayerInfo(
gameProfile,
entry.isListed(),
entry.getLatency(),
GameMode.SURVIVAL,
entry.getDisplayName(),
entry.getChatSession(),
entry.getListOrder(),
entry.isShowHat()
);
modified.add(modifiedPacket);
} //if only the game mode was updated and the packet was modified, don't send anything
}
}
// if the amount of hidden players & modified entries are the same
if (hideCount == modified.size()) {
if (onlyGameMode) { // if only the game mode changed, cancel
event.setCancelled(true);
} else { //if more than the game mode changed, remove the action
wrapper.getActions().remove(WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_GAME_MODE);
event.markForReEncode(true);
}
} else { //modify entries
wrapper.setEntries(modified);
if (modifiedPacket == null) { // if the packet wasn't modified, send original
modified.add(entry);
} else if (!onlyGameMode) { // if more than just the game mode updated, modify the packet
modified.add(modifiedPacket);
} // if only the game mode was updated and the packet was modified, don't send anything
}
// if the amount of hidden players & modified entries are the same
if (hideCount == modified.size()) {
if (onlyGameMode) { // if only the game mode changed, cancel
event.setCancelled(true);
} else { // if more than the game mode changed, remove the action
wrapper.getActions().remove(WrapperPlayServerPlayerInfoUpdate.Action.UPDATE_GAME_MODE);
event.markForReEncode(true);
}
} else { // modify entries
wrapper.setEntries(modified);
event.markForReEncode(true);
}
}
}
@@ -16,8 +16,12 @@ import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.item.enchantment.type.EnchantmentTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAttack;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientSpectateEntity;
public class PacketPlayerAttack extends PacketListenerAbstract {
@@ -33,81 +37,124 @@ public class PacketPlayerAttack extends PacketListenerAbstract {
if (player == null) return;
// The entity does not exist
if (!player.compensatedEntities.entityMap.containsKey(interact.getEntityId()) && !player.compensatedEntities.serverPositionsMap.containsKey(interact.getEntityId())
// the list of entities used to raytrace isn't the same as the list of entities in the world in pre-1.14 (wtf mojang)
&& (!player.compensatedEntities.entitiesRemovedThisTick.contains(interact.getEntityId()) || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14))) {
final BadPacketsW badPacketsW = player.checkManager.getCheck(BadPacketsW.class);
if (badPacketsW.flagAndAlert("entityId=" + interact.getEntityId()) && badPacketsW.shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
return;
}
int entityId = interact.getEntityId();
if (interact.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
if (player.isResetItemUsageOnAttack()) {
GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer);
}
onAttack(event, player, entityId);
} else {
if (isInvalidEntity(event, player, entityId)) return;
// This is not vanilla behaviour as the attack damage attribute is marked as not synced to the client
// However, plugins can still set this by sending an attributes packet
if (player.compensatedEntities.self.getAttributeValue(Attributes.ATTACK_DAMAGE) <= 0) return;
ItemStack heldItem = player.inventory.getHeldItem();
PacketEntity entity = player.compensatedEntities.getEntity(interact.getEntityId());
if (entity != null && (!entity.isLivingEntity || entity.type == EntityTypes.PLAYER || entity.type == EntityTypes.PAINTING
|| entity.type == EntityTypes.ENDER_DRAGON && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2))) {
int knockbackLevel = player.getClientVersion().isOlderThan(ClientVersion.V_1_21) && heldItem != null
? heldItem.getEnchantmentLevel(EnchantmentTypes.KNOCKBACK)
: 0;
final boolean hasNegativeKB = knockbackLevel < 0;
final boolean isLegacyPlayer = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8);
// assume cooldown is full on 1.8 servers
final boolean sufficientCooldownProgress = isLegacyPlayer || player.attackCooldown.getMinimumProgress() > 0.9F;
if (!isLegacyPlayer) {
knockbackLevel = Math.max(knockbackLevel, 0);
if (interact.getAction() == WrapperPlayClientInteractEntity.InteractAction.INTERACT) {
// Interacting with a horse in versions 1.13- will cause the client to
// set the player's rotation to the horse's rotation
if (player.compensatedEntities.getEntity(entityId) instanceof PacketEntityHorse
&& player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_13)) {
player.packetStateData.horseInteractCausedForcedRotation = true;
}
// 1.8 players who are packet sprinting WILL get slowed
// 1.9+ players who are packet sprinting might not, based on attack cooldown
// Players with knockback enchantments always get slowed
if (player.lastSprinting && !hasNegativeKB && sufficientCooldownProgress || knockbackLevel > 0) {
player.minAttackSlow++;
player.maxAttackSlow++;
// Players cannot slow themselves twice in one tick without a knockback sword
if (knockbackLevel == 0) {
player.maxAttackSlow = player.minAttackSlow = 1;
}
} else if (!isLegacyPlayer && player.lastSprinting) {
// 1.9+ players who have attack speed cannot slow themselves twice in one tick because their attack cooldown gets reset on swing.
if (player.maxAttackSlow > 0
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
&& player.compensatedEntities.self.getAttributeValue(Attributes.ATTACK_SPEED) < 16) { // 16 is a reasonable limit
return;
}
// 1.9+ player who might have been slowed, but we can't be sure
player.maxAttackSlow++;
}
}
if (player.gamemode != GameMode.SPECTATOR) {
player.attackCooldown.reset();
}
} else if (interact.getAction() == WrapperPlayClientInteractEntity.InteractAction.INTERACT) {
// Interacting with a horse in versions 1.13- will cause the client to
// set the player's rotation to the horse's rotation
if (player.compensatedEntities.getEntity(interact.getEntityId()) instanceof PacketEntityHorse
&& player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_13)) {
player.packetStateData.horseInteractCausedForcedRotation = true;
}
}
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
if (packet.getAction() == DiggingAction.STAB) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
if (player.isResetItemUsageOnAttack()) {
GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer);
}
}
}
if (event.getPacketType() == PacketType.Play.Client.ATTACK) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
WrapperPlayClientAttack packet = new WrapperPlayClientAttack(event);
onAttack(event, player, packet.getEntityId());
}
if (event.getPacketType() == PacketType.Play.Client.SPECTATE_ENTITY) {
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
if (player == null) return;
WrapperPlayClientSpectateEntity packet = new WrapperPlayClientSpectateEntity(event);
onAttack(event, player, packet.getEntityId());
}
}
private void onAttack(PacketReceiveEvent event, GrimPlayer player, int entityId) {
if (isInvalidEntity(event, player, entityId)) return;
if (player.isResetItemUsageOnAttack()) {
GrimAPI.INSTANCE.getItemResetHandler().resetItemUsage(player.platformPlayer);
}
// This is not vanilla behaviour as the attack damage attribute is marked as not synced to the client
// However, plugins can still set this by sending an attributes packet
if (player.compensatedEntities.self.getAttributeValue(Attributes.ATTACK_DAMAGE) <= 0) return;
ItemStack heldItem = player.inventory.getHeldItem();
PacketEntity entity = player.compensatedEntities.getEntity(entityId);
if (entity != null && (!entity.isLivingEntity || entity.type == EntityTypes.PLAYER || entity.type == EntityTypes.PAINTING
|| entity.type == EntityTypes.ENDER_DRAGON && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2))) {
int knockbackLevel = player.getClientVersion().isOlderThan(ClientVersion.V_1_21) && heldItem != null
? heldItem.getEnchantmentLevel(EnchantmentTypes.KNOCKBACK)
: 0;
final boolean hasNegativeKB = knockbackLevel < 0;
final boolean isLegacyPlayer = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8);
// assume cooldown is full on 1.8 servers
final boolean sufficientCooldownProgress = isLegacyPlayer || player.attackCooldown.getMinimumProgress() > 0.9F;
if (!isLegacyPlayer) {
knockbackLevel = Math.max(knockbackLevel, 0);
}
// 1.8 players who are packet sprinting WILL get slowed
// 1.9+ players who are packet sprinting might not, based on attack cooldown
// Players with knockback enchantments always get slowed
if (player.lastSprinting && !hasNegativeKB && sufficientCooldownProgress || knockbackLevel > 0) {
player.minAttackSlow++;
player.maxAttackSlow++;
// Players cannot slow themselves twice in one tick without a knockback sword
if (knockbackLevel == 0) {
player.maxAttackSlow = player.minAttackSlow = 1;
}
} else if (!isLegacyPlayer && player.lastSprinting) {
// 1.9+ players who have attack speed cannot slow themselves twice in one tick because their attack cooldown gets reset on swing.
if (player.maxAttackSlow > 0
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
&& player.compensatedEntities.self.getAttributeValue(Attributes.ATTACK_SPEED) < 16) { // 16 is a reasonable limit
return;
}
// 1.9+ player who might have been slowed, but we can't be sure
player.maxAttackSlow++;
}
}
if (player.gamemode != GameMode.SPECTATOR) {
player.attackCooldown.reset();
}
}
private boolean isInvalidEntity(PacketReceiveEvent event, GrimPlayer player, int entityId) {
// The entity does not exist
if (!player.compensatedEntities.entityMap.containsKey(entityId) && !player.compensatedEntities.serverPositionsMap.containsKey(entityId)
// the list of entities used to raytrace isn't the same as the list of entities in the world in pre-1.14 (wtf mojang)
&& (!player.compensatedEntities.entitiesRemovedThisTick.contains(entityId) || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14))) {
final BadPacketsW badPacketsW = player.checkManager.getCheck(BadPacketsW.class);
if (badPacketsW.flagAndAlert("entityId=" + entityId) && badPacketsW.shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
return true;
}
return false;
}
}
@@ -113,6 +113,7 @@ public class CheckManager {
.put(ExploitB.class, new ExploitB(player))
.put(BadPacketsA.class, new BadPacketsA(player))
.put(BadPacketsB.class, new BadPacketsB(player))
.put(BadPacketsC.class, new BadPacketsC(player))
.put(BadPacketsD.class, new BadPacketsD(player))
.put(BadPacketsE.class, new BadPacketsE(player))
.put(BadPacketsF.class, new BadPacketsF(player))
@@ -30,6 +30,30 @@ public class DatabaseUtils {
return new UUID(msb, lsb);
}
/**
* Retrieves a UUID from a ResultSet.
* This handles both PostgreSQL (which returns a native {@link UUID} object)
* and MySQL/SQLite (which return a {@code byte[]}).
*
* @param resultSet The ResultSet to read from.
* @param columnName The name of the UUID column.
* @return The UUID.
* @throws SQLException If a database access error occurs, or if the UUID column
* contains an unexpected type or invalid data.
*/
public static UUID getUuid(ResultSet resultSet, String columnName) throws SQLException {
Object uuidObject = resultSet.getObject(columnName);
if (uuidObject instanceof byte[] uuidBytes) {
return bytesToUuid(uuidBytes);
} else if (uuidObject instanceof UUID uuid) {
return uuid;
}
throw new SQLException("Unexpected UUID type: " + (uuidObject == null ? "null" : uuidObject.getClass().getName()));
}
// --- Generic Deduplication Lookup (uses DatabaseDialect) ---
public static long getOrCreateId(Connection connection, DatabaseDialect dialect, String tableName, String stringColumnName, String value) throws SQLException {
// Step 1: Attempt to insert the string.
@@ -13,8 +13,7 @@ public record Violation(String server, UUID uuid, String checkName, String verbo
List<Violation> violations = new ArrayList<>();
while (resultSet.next()) {
String server = resultSet.getString(DatabaseConstants.SERVERS_STRING_COLUMN);
byte[] uuidBytes = resultSet.getBytes(DatabaseConstants.VIOLATIONS_UUID_COLUMN);
UUID uuid = DatabaseUtils.bytesToUuid(uuidBytes);
UUID uuid = DatabaseUtils.getUuid(resultSet, DatabaseConstants.VIOLATIONS_UUID_COLUMN);
String checkName = resultSet.getString(DatabaseConstants.CHECK_NAMES_STRING_COLUMN);
String verbose = resultSet.getString(DatabaseConstants.VIOLATIONS_VERBOSE_COLUMN);
int vl = resultSet.getInt(DatabaseConstants.VIOLATIONS_VL_COLUMN);
@@ -56,9 +56,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.SERVERS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.SERVERS_TABLE + "_name ON " + DatabaseConstants.SERVERS_TABLE + "(" + DatabaseConstants.SERVERS_STRING_COLUMN + ");"
).execute();
// 2. Create Lookup Table for Check Names
connection.prepareStatement(
@@ -67,9 +64,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.CHECK_NAMES_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CHECK_NAMES_TABLE + "_string ON " + DatabaseConstants.CHECK_NAMES_TABLE + "(" + DatabaseConstants.CHECK_NAMES_STRING_COLUMN + ");"
).execute();
// --- NEW LOOKUP TABLES ---
// 3. Create Lookup Table for Grim Versions
@@ -79,9 +73,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.GRIM_VERSIONS_TABLE + "_string ON " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(" + DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN + ");"
).execute();
// 4. Create Lookup Table for Client Brands
connection.prepareStatement(
@@ -90,9 +81,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CLIENT_BRANDS_TABLE + "_string ON " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(" + DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN + ");"
).execute();
// 5. Create Lookup Table for Client Versions
connection.prepareStatement(
@@ -101,9 +89,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CLIENT_VERSIONS_TABLE + "_string ON " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(" + DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN + ");"
).execute();
// 6. Create Lookup Table for Server Versions
connection.prepareStatement(
@@ -112,9 +97,6 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.SERVER_VERSIONS_TABLE + "_string ON " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(" + DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN + ");"
).execute();
// --- END NEW LOOKUP TABLES ---
// 7. Create Main Violations Table with ALL Foreign Keys and optimized UUID
@@ -127,44 +109,30 @@ public class MySQLViolationDatabase implements ViolationDatabase {
DatabaseConstants.VIOLATIONS_VERBOSE_COLUMN + " TEXT NOT NULL, " +
DatabaseConstants.VIOLATIONS_VL_COLUMN + " INT NOT NULL, " +
DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + " BIGINT NOT NULL, " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + ") REFERENCES " + DatabaseConstants.SERVERS_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CHECK_NAMES_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(id)" + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(id)" +
")"
).execute();
// 8. Create Indexes for efficient querying on main table (includes new FKs)
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_uuid ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_UUID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_created_at ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_server_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_check_name_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_grim_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_client_brand_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_client_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_server_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + ");" // NEW
).execute();
try {
connection.prepareStatement(
"CREATE INDEX idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_uuid_created_at ON " +
DatabaseConstants.VIOLATIONS_TABLE + "(" +
DatabaseConstants.VIOLATIONS_UUID_COLUMN + ", " +
DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN +
")"
).execute();
} catch (SQLException ignored) {}
} catch (SQLException ex) {
LogUtil.error("Failed to generate violations database:", ex);
@@ -6,6 +6,7 @@ import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.webhook.discord.CompiledDiscordTemplate;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.experimental.UtilityClass;
@@ -27,7 +28,7 @@ public class MessageUtil {
private final Pattern HEX_PATTERN = Pattern.compile("([&§]#[A-Fa-f0-9]{6})|([&§]x([&§][A-Fa-f0-9]){6})");
private final char PLACEHOLDER_ESCAPE_CHAR = '\uFFFF'; // this specific character holds no significance
public @NotNull String toUnlabledString(@Nullable Vector3i vec) {
public @NotNull String toUnlabledString(@Nullable Vector3d vec) {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
@@ -35,6 +36,10 @@ public class MessageUtil {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
public @NotNull String toUnlabledString(@Nullable Vector3i vec) {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
@Contract("_, null, _ -> null; _, !null, _ -> !null")
public @Nullable String replacePlaceholders(@Nullable GrimPlayer player, @Nullable String string, boolean removeFormatting) {
return replacePlaceholders(player, player == null ? null : player.platformPlayer, string, removeFormatting);
@@ -816,7 +816,7 @@ public enum CollisionData implements CollisionFactory {
}, StateTypes.TRIPWIRE_HOOK),
TORCH(new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D),
StateTypes.TORCH, StateTypes.REDSTONE_TORCH, StateTypes.COPPER_TORCH),
StateTypes.TORCH, StateTypes.REDSTONE_TORCH, StateTypes.COPPER_TORCH, StateTypes.SOUL_TORCH),
WALL_TORCH((player, version, data, x, y, z) -> switch (data.getFacing()) {
case NORTH -> new HexCollisionBox(5.5D, 3.0D, 11.0D, 10.5D, 13.0D, 16.0D);
@@ -825,7 +825,7 @@ public enum CollisionData implements CollisionFactory {
case EAST -> new HexCollisionBox(0.0D, 3.0D, 5.5D, 5.0D, 13.0D, 10.5D);
// 1.13 separates wall and normal torches, 1.12 does not
default -> new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D);
}, StateTypes.WALL_TORCH, StateTypes.REDSTONE_WALL_TORCH, StateTypes.COPPER_WALL_TORCH),
}, StateTypes.WALL_TORCH, StateTypes.REDSTONE_WALL_TORCH, StateTypes.COPPER_WALL_TORCH, StateTypes.SOUL_WALL_TORCH),
// 1.17 blocks
CANDLE((player, version, data, x, y, z) -> {
@@ -9,7 +9,7 @@ import java.util.UUID;
public class PacketEntityRideable extends PacketEntity {
public boolean hasSaddle = false;
public boolean hasSaddle = false; // We dont actually use this anywhere, why is this here?
public int boostTimeMax = 0;
public int currentBoostTime = 0;
@@ -242,6 +242,11 @@ public class CompensatedEntities {
PacketEntity entity = player.compensatedEntities.getEntity(entityID);
if (entity == null) return;
// 26.1 added AGE_LOCKED to AgeableMob after BABY, shifting all descendant field indices +1.
// Only affects entities extending AgeableMob (horses, pigs, striders, tamables, etc.)
final int ageableOffset = PacketEvents.getAPI().getServerManager().getVersion()
.isNewerThanOrEquals(ServerVersion.V_26_1) ? 1 : 0;
if (entity.isAgeable) {
int id;
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_8_8)) {
@@ -255,7 +260,7 @@ public class CompensatedEntities {
} else if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_16_5)) {
id = 15;
} else {
id = 16;
id = 16; // BABY is still at 16 in 26.1 (AGE_LOCKED is 17, after BABY)
}
// 1.14 good
@@ -320,7 +325,10 @@ public class CompensatedEntities {
shulker.facing = BlockFace.valueOf(shulkerAttached.getValue().toString().toUpperCase());
}
EntityData<?> height = WatchableIndexUtil.getIndex(watchableObjects, id + 2);
// ATTACH_POS was removed in 1.17, shifting PEEK from id+2 to id+1
int peekOffset = PacketEvents.getAPI().getServerManager().getVersion()
.isNewerThanOrEquals(ServerVersion.V_1_17) ? 1 : 2;
EntityData<?> height = WatchableIndexUtil.getIndex(watchableObjects, id + peekOffset);
if (height != null) {
if ((byte) height.getValue() == 0) {
ShulkerData data = new ShulkerData(shulker, player.lastTransactionSent.get(), true);
@@ -354,34 +362,42 @@ public class CompensatedEntities {
}
if (entity.type == EntityTypes.PIG) {
// SADDLE removed in 1.21.5, shifting BOOST_TIME from 18→17.
// ageableOffset naturally cancels this out in 26.1 (BOOST_TIME back at 18).
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5))
offset = 1;
EntityData<?> pigSaddle = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset);
if (pigSaddle != null) {
rideable.hasSaddle = (boolean) pigSaddle.getValue();
// SADDLE removed in 1.21.5; now handled via equipment
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_21_5)) {
EntityData<?> pigSaddle = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset + ageableOffset);
if (pigSaddle != null) {
rideable.hasSaddle = (boolean) pigSaddle.getValue();
}
}
EntityData<?> pigBoost = WatchableIndexUtil.getIndex(watchableObjects, 18 - offset);
EntityData<?> pigBoost = WatchableIndexUtil.getIndex(watchableObjects, 18 - offset + ageableOffset);
if (pigBoost != null) { // What does 1.9-1.10 do here? Is this feature even here?
rideable.boostTimeMax = (int) pigBoost.getValue();
rideable.currentBoostTime = 0;
}
} else if (entity instanceof PacketEntityStrider) {
EntityData<?> striderBoost = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset);
EntityData<?> striderBoost = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset + ageableOffset);
if (striderBoost != null) {
rideable.boostTimeMax = (int) striderBoost.getValue();
rideable.currentBoostTime = 0;
}
EntityData<?> striderShaking = WatchableIndexUtil.getIndex(watchableObjects, 18 - offset);
EntityData<?> striderShaking = WatchableIndexUtil.getIndex(watchableObjects, 18 - offset + ageableOffset);
if (striderShaking != null) {
((PacketEntityStrider) rideable).isShaking = (boolean) striderShaking.getValue();
}
EntityData<?> striderSaddle = WatchableIndexUtil.getIndex(watchableObjects, 19 - offset);
if (striderSaddle != null) {
rideable.hasSaddle = (boolean) striderSaddle.getValue();
// SADDLE removed in 1.21.5; now handled via equipment
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_21_5)) {
EntityData<?> striderSaddle = WatchableIndexUtil.getIndex(watchableObjects, 19 - offset + ageableOffset);
if (striderSaddle != null) {
rideable.hasSaddle = (boolean) striderSaddle.getValue();
}
}
}
}
@@ -400,7 +416,7 @@ public class CompensatedEntities {
offset = 1;
}
EntityData<?> horseByte = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset);
EntityData<?> horseByte = WatchableIndexUtil.getIndex(watchableObjects, 17 - offset + ageableOffset);
if (horseByte != null) {
byte info = (byte) horseByte.getValue();
@@ -412,7 +428,7 @@ public class CompensatedEntities {
// track camel dashing
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20)) {
if (entity instanceof PacketEntityCamel camel) {
EntityData<?> entityData = WatchableIndexUtil.getIndex(watchableObjects, 18);
EntityData<?> entityData = WatchableIndexUtil.getIndex(watchableObjects, 18 + ageableOffset);
if (entityData != null) {
camel.setDashing((boolean) entityData.getValue());
@@ -437,7 +453,7 @@ public class CompensatedEntities {
}
if (entity instanceof PacketEntityNautilus nautilus) {
EntityData<?> entityData = WatchableIndexUtil.getIndex(watchableObjects, 19);
EntityData<?> entityData = WatchableIndexUtil.getIndex(watchableObjects, 19 + ageableOffset);
if (entityData != null) {
nautilus.setDashing((boolean) entityData.getValue());
@@ -403,7 +403,8 @@ public class CompensatedInventory extends Check implements PacketCheck {
// 01/07/2025: Somehow, the server sends a window id 0 update when the player is not in their inventory?
// I guess just revert isPacketInventoryActive if the player has a NotImplementedMenu open?
// Regardless, the client does accept this packet and update its inventory, so we must do the same.
if (slots.size() == cachedPacketInvSize || items.getWindowId() == 0) {
boolean forceUpdate = slots.size() == cachedPacketInvSize || items.getWindowId() == 0;
if (!isPacketInventoryActive && forceUpdate) {
isPacketInventoryActive = true;
updatedValue.set(true);
}
@@ -110,7 +110,7 @@ public final class BoundingBoxSize {
return 0.3125f;
} else if (type == EntityTypes.SPIDER) {
return 1.4f;
} else if (type == EntityTypes.SQUID) {
} else if (type == EntityTypes.SQUID || type == EntityTypes.GLOW_SQUID) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.8f : 0.95f;
} else if (type == EntityTypes.TURTLE) {
return 1.2f;
@@ -251,7 +251,7 @@ public final class BoundingBoxSize {
return 0.6f;
} else if (type == EntityTypes.EVOKER_FANGS || type == EntityTypes.VEX) {
return 0.8f;
} else if (type == EntityTypes.SQUID) {
} else if (type == EntityTypes.SQUID || type == EntityTypes.GLOW_SQUID) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.8f : 0.95f;
} else if (type == EntityTypes.PARROT || type == EntityTypes.BAT || type == EntityTypes.PIG || type == EntityTypes.SPIDER) {
return 0.9f;
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -26,6 +26,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -27,6 +27,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+2 -1
View File
@@ -7,7 +7,7 @@
alerts:
# Czy oprócz wysyłania alertów do graczy, powinny być one również wysyłane na konsolę?
print-to-console: true
# Określa, czy i w jaki sposób alerty są udostępniane pomiędzy serwerami połączonymi z Twoim serwerem proxy.
# Określa, czy i w jaki sposób alerty są udostępniane pomiędzy serwerami połączonymi z Twoim serwerem proxy.
# Jeśli korzystasz z Velocity, musisz włączyć opcję „bungee-plugin-message-channel” w konfiguracji Velocity.
proxy:
# Czy alerty powinny być wysyłane do innych serwerów podłączonych do Twojego serwera proxy?
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -26,6 +26,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -25,6 +25,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1
View File
@@ -26,6 +26,7 @@ client-brand:
ignored-clients:
- "^vanilla$"
- "^fabric$"
- "^quilt$"
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
- "^Feather Fabric$"
- "^labymod$"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+2 -2
View File
@@ -12,7 +12,7 @@ cloud-processors-requirements = "1.0.0-rc.1"
floodgate-api = "2.0-SNAPSHOT"
geyser-base-api = "1.0.2"
grim-api = "1.2.4.0"
packetevents = "2.11.2+675bc5c-SNAPSHOT"
packetevents = "2.12.1+d82ae73-SNAPSHOT"
placeholderapi = "2.11.6"
viaversion = "5.7.1"
@@ -26,7 +26,7 @@ configuralize = "1.4.1" # :slim classifier is applied in build.gradle.kts
# --- Build & Tooling ---
fabric-loader = "0.16.14"
fabric-loom = "1.14.6"
fabric-loom = "1.15.5"
lombok = "9.0.0"
shadow = "9.3.0"
spotless = "6.25.0"