Merge PR #3 upstream updates while preserving legacy item bobbing fix
This commit was merged in pull request #3.
This commit is contained in:
commit
67da895f25
101 files changed
+1647
-722
No files matched your search
@@ -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 }}"
|
||||
+1
-1
@@ -20,7 +20,7 @@ import versioning.VersionUtil
|
||||
|
||||
BuildConfig.init(project)
|
||||
|
||||
val baseVersion = "2.3.73"
|
||||
val baseVersion = "2.3.74"
|
||||
group = "ac.grim.grimac"
|
||||
version = VersionUtil.computeVersion(baseVersion)
|
||||
description = "Libre simulation anticheat designed for 1.21 with 1.8–1.21 support, powered by PacketEvents 2.11.2."
|
||||
|
||||
@@ -27,12 +27,9 @@ repositories {
|
||||
includeGroup("me.clip")
|
||||
}
|
||||
|
||||
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
|
||||
includeGroup("com.github.retrooper")
|
||||
}
|
||||
|
||||
exclusive("https://repo.grim.ac/snapshots") {
|
||||
includeGroup("ac.grim.grimac")
|
||||
includeGroup("com.github.retrooper")
|
||||
}
|
||||
|
||||
exclusive("https://nexus.scarsz.me/content/repositories/releases", { mavenContent { releasesOnly() } }) {
|
||||
@@ -82,7 +79,6 @@ bukkit {
|
||||
"floodgate",
|
||||
"FastLogin",
|
||||
"PlaceholderAPI",
|
||||
"ZNPCsPlus",
|
||||
)
|
||||
|
||||
permissions {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.grim.grimac.platform.bukkit.player;
|
||||
|
||||
import ac.grim.grimac.platform.api.entity.GrimEntity;
|
||||
import ac.grim.grimac.platform.api.player.BlockTranslator;
|
||||
import ac.grim.grimac.platform.api.player.PlatformInventory;
|
||||
import ac.grim.grimac.platform.api.player.PlatformPlayer;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
@@ -170,6 +171,11 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
return GrimACBukkitLoaderPlugin.LOADER.getBukkitSenderFactory().map(this.bukkitPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockTranslator getBlockTranslator() {
|
||||
return BlockTranslator.IDENTITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Player getNative() {
|
||||
|
||||
@@ -10,13 +10,9 @@ repositories {
|
||||
// We still call mavenLocal() conditionally at the top for non-exclusive deps (general fallback)
|
||||
if (BuildConfig.mavenLocalOverride) mavenLocal()
|
||||
|
||||
// Grim API
|
||||
// Grim API & PacketEvents
|
||||
exclusive("https://repo.grim.ac/snapshots") {
|
||||
includeGroup("ac.grim.grimac")
|
||||
}
|
||||
|
||||
// PacketEvents
|
||||
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
|
||||
includeGroup("com.github.retrooper")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
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 lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -175,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) {
|
||||
@@ -207,4 +209,10 @@ public class Check extends GrimProcessor implements AbstractCheck {
|
||||
return isFlying(packetType);
|
||||
}
|
||||
|
||||
// prevent causing exploits with packet cancelling (ie noslow)
|
||||
public boolean canCancel(DiggingAction action) {
|
||||
return action != DiggingAction.RELEASE_USE_ITEM
|
||||
// we check client version here because 1.8- doesn't predict dropping items, so we can cancel them. (see CompensatedInventory)
|
||||
&& (action != DiggingAction.DROP_ITEM && action != DiggingAction.DROP_ITEM_STACK || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8));
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,9 @@ public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
* @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 baseX Base x coordinate before eye height adjustments
|
||||
* @param baseY Base y coordinate before eye height adjustments
|
||||
* @param baseZ Base z coordinate before eye height adjustments
|
||||
* @param isPrediction Whether these hitboxes are from a prediction calculation
|
||||
*
|
||||
* Packet Format (Version 1):
|
||||
@@ -99,7 +101,7 @@ public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
* - Double: maxZ
|
||||
*/
|
||||
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
|
||||
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, Vector3dm basePos,
|
||||
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, double baseX, double baseY, double baseZ,
|
||||
boolean isPrediction, double reachDistance) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
@@ -119,7 +121,7 @@ public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
buffer.writeDouble(reachDistance);
|
||||
|
||||
// Write base position
|
||||
writeVector(buffer, basePos);
|
||||
writeVector(buffer, baseX, baseY, baseZ);
|
||||
|
||||
// Write number of ray traces
|
||||
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
|
||||
@@ -133,7 +135,7 @@ public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
// 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);
|
||||
writeVector(buffer, lookVec.getX(), lookVec.getY(), lookVec.getZ());
|
||||
}
|
||||
|
||||
// Write number of hitboxes
|
||||
@@ -193,13 +195,15 @@ public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to write a Vector to the ByteBuf
|
||||
* Helper method to write vector coordinates to the ByteBuf
|
||||
* @param buffer The buffer to write to
|
||||
* @param vector The vector to write
|
||||
* @param x The x coordinate to write
|
||||
* @param y The y coordinate to write
|
||||
* @param z The z coordinate to write
|
||||
*/
|
||||
private void writeVector(ByteBuf buffer, Vector3dm vector) {
|
||||
buffer.writeDouble(vector.getX());
|
||||
buffer.writeDouble(vector.getY());
|
||||
buffer.writeDouble(vector.getZ());
|
||||
private void writeVector(ByteBuf buffer, double x, double y, double z) {
|
||||
buffer.writeDouble(x);
|
||||
buffer.writeDouble(y);
|
||||
buffer.writeDouble(z);
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,21 @@ 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.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@CheckData(name = "BadPacketsC", description = "Interacted with self")
|
||||
@CheckData(name = "BadPacketsC", description = "Tried to wake up while not sleeping", experimental = true)
|
||||
public class BadPacketsC extends Check implements PacketCheck {
|
||||
public BadPacketsC(GrimPlayer player) {
|
||||
public BadPacketsC(@NotNull GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY
|
||||
&& player.cameraEntity.isSelf()
|
||||
&& new WrapperPlayClientInteractEntity(event).getEntityId() == player.entityID
|
||||
&& flagAndAlert() && shouldModifyPackets() // Instant ban
|
||||
) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
|
||||
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.LEAVE_BED
|
||||
&& !player.isInBed) {
|
||||
flagAndAlert();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class BadPacketsL extends Check implements PacketCheck {
|
||||
+ ", face=" + packet.getBlockFace()
|
||||
+ ", sequence=" + packet.getSequence()
|
||||
+ ", action=" + packet.getAction().toString().toLowerCase(Locale.ROOT)
|
||||
) && shouldModifyPackets() && packet.getAction() != DiggingAction.RELEASE_USE_ITEM) {
|
||||
) && shouldModifyPackets() && canCancel(packet.getAction())) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCl
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow.WindowClickType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
|
||||
|
||||
@CheckData(name = "BadPacketsP", description = "Invalid click packets", experimental = true)
|
||||
@CheckData(name = "BadPacketsP", description = "Invalid window click packet", experimental = true)
|
||||
public class BadPacketsP extends Check implements PacketCheck {
|
||||
|
||||
private int containerType = -1;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,8 +31,10 @@ import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntitySizeable;
|
||||
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtils;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.nmsutil.WorldRayTrace;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
@@ -48,10 +50,11 @@ 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.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAttack;
|
||||
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 com.viaversion.viaversion.api.Via;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -87,63 +90,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 itemInHand = player.inventory.getItemInHand(hand);
|
||||
|
||||
boolean tooManyAttacks = playerAttackQueue.size() > 10;
|
||||
if (!tooManyAttacks) {
|
||||
playerAttackQueue.put(action.getEntityId(), new InteractionData(new Vector3d(player.x, player.y, player.z), itemInHand)); // Queue for next tick for very precise check
|
||||
}
|
||||
|
||||
boolean knownInvalid = isKnownInvalid(entity, itemInHand);
|
||||
|
||||
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
|
||||
@@ -153,6 +107,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
|
||||
//
|
||||
@@ -161,7 +218,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
// than this method. If this method flags, the other method WILL flag.
|
||||
//
|
||||
// Meaning that the other check should be the only one that flags.
|
||||
private boolean isKnownInvalid(PacketEntity reachEntity, ItemStack itemInHand) {
|
||||
private boolean isKnownInvalid(PacketEntity reachEntity, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin) {
|
||||
// If the entity doesn't exist, or if it is exempt, or if it is dead
|
||||
if ((blacklisted.contains(reachEntity.type) || !reachEntity.isLivingEntity) && reachEntity.type != EntityTypes.END_CRYSTAL)
|
||||
return false; // exempt
|
||||
@@ -172,13 +229,12 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
// Filter out what we assume to be cheats
|
||||
if (cancelBuffer != 0) {
|
||||
CheckResult result = checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), itemInHand, true);
|
||||
CheckResult result = checkReach(reachEntity, player.x, player.y, player.z, hasAttackRange, itemMaxReach, itemHitboxMargin, true);
|
||||
return result.isFlag(); // If they flagged
|
||||
} else {
|
||||
SimpleCollisionBox targetBox = getTargetBox(reachEntity);
|
||||
|
||||
double maxReach = applyReachModifiers(targetBox, itemInHand, !player.packetStateData.didLastMovementIncludePosition);
|
||||
|
||||
double maxReach = applyReachModifiers(targetBox, hasAttackRange, itemMaxReach, itemHitboxMargin, !player.packetStateData.didLastMovementIncludePosition);
|
||||
return ReachUtils.getMinReachToBox(player, targetBox) > maxReach;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +245,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
if (reachEntity == null) continue;
|
||||
|
||||
InteractionData interactionData = attack.getValue();
|
||||
CheckResult result = checkReach(reachEntity, interactionData.vector(), interactionData.itemInHand(), false);
|
||||
CheckResult result = checkReach(reachEntity, interactionData.x, interactionData.y, interactionData.z, interactionData.hasAttackRange, interactionData.maxReach, interactionData.hitboxMargin, false);
|
||||
switch (result.type()) {
|
||||
case REACH -> {
|
||||
String added = ", type=" + reachEntity.type.getName().getKey();
|
||||
@@ -223,10 +279,10 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, ItemStack itemInHand, boolean isPrediction) {
|
||||
private CheckResult checkReach(PacketEntity reachEntity, double x, double y, double z, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin, boolean isPrediction) {
|
||||
SimpleCollisionBox targetBox = getTargetBox(reachEntity);
|
||||
|
||||
double maxReach = applyReachModifiers(targetBox, itemInHand, !player.packetStateData.didLastLastMovementIncludePosition);
|
||||
double maxReach = applyReachModifiers(targetBox, hasAttackRange, itemMaxReach, itemHitboxMargin, !player.packetStateData.didLastLastMovementIncludePosition);
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
|
||||
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
|
||||
@@ -236,13 +292,12 @@ public class Reach extends Check implements PacketCheck {
|
||||
// +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());
|
||||
final Vector3dm eyePos = new Vector3dm(x, 0, z);
|
||||
for (Vector3dm lookVec : possibleLookDirs) {
|
||||
for (double eye : possibleEyeHeights) {
|
||||
eyePos.setY(from.getY() + eye);
|
||||
eyePos.setY(y + eye);
|
||||
Vector3dm endReachPos = eyePos.clone().add(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance);
|
||||
|
||||
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
|
||||
@@ -260,7 +315,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
|
||||
if (hitboxDebuggingEnabled())
|
||||
sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction);
|
||||
sendHitboxDebugData(reachEntity, x, y, z, 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)
|
||||
@@ -268,7 +323,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
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);
|
||||
final @Nullable Pair<Double, HitData> hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, x, y, z);
|
||||
HitData hitData = hitResult.second();
|
||||
// If the returned hit result was NOT the target entity we flag the check
|
||||
if (hitData instanceof EntityHitData &&
|
||||
@@ -312,20 +367,16 @@ public class Reach extends Check implements PacketCheck {
|
||||
return reachEntity.getPossibleCollisionBoxes();
|
||||
}
|
||||
|
||||
private double applyReachModifiers(SimpleCollisionBox targetBox, ItemStack itemInHand, boolean giveMovementThreshold) {
|
||||
private static final boolean ATTACK_RANGE_COMPONENT_EXISTS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_11);
|
||||
private static final boolean USE_1_8_HITBOX_MARGIN = PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_8_8);
|
||||
|
||||
private double applyReachModifiers(SimpleCollisionBox targetBox, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin, boolean giveMovementThreshold) {
|
||||
double maxReach;
|
||||
double hitboxMargin = threshold;
|
||||
|
||||
ItemAttackRange attackRange = null;
|
||||
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11) && ATTACK_RANGE_COMPONENT_EXISTS) {
|
||||
// TODO: ViaVersion support https://github.com/ViaVersion/ViaVersion/pull/4733
|
||||
attackRange = itemInHand.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
|
||||
}
|
||||
|
||||
if (attackRange != null) {
|
||||
maxReach = attackRange.getMaxRange();
|
||||
hitboxMargin += attackRange.getHitboxMargin();
|
||||
if (hasAttackRange) {
|
||||
maxReach = itemMaxReach;
|
||||
hitboxMargin += itemHitboxMargin;
|
||||
} else {
|
||||
maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
|
||||
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
|
||||
@@ -349,8 +400,6 @@ public class Reach extends Check implements PacketCheck {
|
||||
return maxReach;
|
||||
}
|
||||
|
||||
private static final boolean ATTACK_RANGE_COMPONENT_EXISTS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_11);
|
||||
|
||||
@Override
|
||||
public void onReload(ConfigManager config) {
|
||||
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
|
||||
@@ -371,7 +420,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
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;
|
||||
if (GrimMath.distanceSquared(vector3i.x, vector3i.y, vector3i.z, player.x, player.y, player.z) > 36) return;
|
||||
// Only do this if the state really had any world impact
|
||||
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
|
||||
blocksChangedThisTick.add(vector3i);
|
||||
@@ -381,7 +430,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
|
||||
}
|
||||
|
||||
private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
|
||||
private void sendHitboxDebugData(PacketEntity reachEntity, double x, double y, double z, 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();
|
||||
@@ -428,11 +477,10 @@ public class Reach extends Check implements PacketCheck {
|
||||
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
|
||||
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
|
||||
lookVecsAndEyeHeights,
|
||||
new Vector3dm(from.getX(), from.getY(), from.getZ()),
|
||||
x, y, z,
|
||||
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
|
||||
}
|
||||
|
||||
private record InteractionData(Vector3d vector, ItemStack itemInHand) {
|
||||
private record InteractionData(double x, double y, double z, boolean hasAttackRange, float maxReach, float hitboxMargin) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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;
|
||||
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 {
|
||||
public SelfInteract(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
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) {
|
||||
|
||||
@@ -41,10 +41,6 @@ public class GhostBlockMitigation extends BlockPlaceCheck {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == xAgainst && j == yAgainst && k == zAgainst) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!world.isChunkLoaded(i >> 4, k >> 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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,7 +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 {
|
||||
@@ -41,7 +46,8 @@ public class PacketOrderB extends Check implements PacketCheck {
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (exempt) return;
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.ANIMATION
|
||||
&& new WrapperPlayClientAnimation(event).getHand() == InteractionHand.MAIN_HAND) {
|
||||
sentAnimationSinceLastAttack = sentAnimation = true;
|
||||
sentAttack = sentSlotSwitch = false;
|
||||
return;
|
||||
@@ -50,17 +56,20 @@ public class PacketOrderB extends Check implements PacketCheck {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -78,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()
|
||||
|
||||
@@ -7,7 +7,6 @@ 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.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
@@ -24,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
|
||||
@@ -32,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"
|
||||
@@ -42,7 +45,7 @@ public class PacketOrderF extends Check implements PostPredictionCheck {
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING
|
||||
&& new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEASE_USE_ITEM
|
||||
&& !canCancel(new WrapperPlayClientPlayerDigging(event).getAction())
|
||||
) return; // don't cause a noslow
|
||||
|
||||
event.setCancelled(true);
|
||||
|
||||
@@ -28,27 +28,26 @@ 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()
|
||||
+ ", digging=" + player.packetOrderProcessor.isDigging();
|
||||
if (!player.canSkipTicks()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets()) {
|
||||
if (flagAndAlert(verbose) && shouldModifyPackets() && canCancel(action)) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.webhook.WebhookMessage;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.WebhookMessage;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,9 +57,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,7 +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 {
|
||||
|
||||
@@ -32,77 +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 noCooldown = isLegacyPlayer || PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_9);
|
||||
|
||||
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 && noCooldown) || 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++;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package ac.grim.grimac.events.packets;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.checks.impl.movement.NoSlow;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.item.ItemBehaviour;
|
||||
import ac.grim.grimac.utils.item.ItemBehaviourRegistry;
|
||||
@@ -212,7 +211,7 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
|
||||
&& player.packetStateData.getSlowedByUsingItemSlot() != player.packetStateData.lastSlotSelected;
|
||||
if (slotChanged || player.inventory.getItemInHand(player.packetStateData.itemInUseHand).isEmpty()) {
|
||||
player.packetStateData.setSlowedByUsingItem(false);
|
||||
if (slotChanged) player.checkManager.getPostPredictionCheck(NoSlow.class).didSlotChangeLastTick = true;
|
||||
if (slotChanged) player.checkManager.getNoSlow().didSlotChangeLastTick = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ public class PacketPlayerRespawn extends PacketListenerAbstract {
|
||||
final boolean keepTrackedData = this.hasFlag(respawn, KEEP_TRACKED_DATA);
|
||||
|
||||
if (!keepTrackedData) {
|
||||
player.food = 20;
|
||||
player.powderSnowFrozenTicks = 0;
|
||||
player.compensatedEntities.self.hasGravity = true;
|
||||
player.playerEntityHasGravity = true;
|
||||
|
||||
@@ -113,6 +113,11 @@ public class PacketServerTeleport extends PacketListenerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
// 1.21.2+ client ignore teleports if player is inside vehicle, ABSOLUTE CINEMA MOJANG
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) && player.compensatedEntities.serverPlayerVehicle != null) {
|
||||
pos = player.getSetbackTeleportUtil().lastKnownGoodPosition.getPos();
|
||||
}
|
||||
|
||||
player.sendTransaction();
|
||||
final int lastTransactionSent = player.lastTransactionSent.get();
|
||||
event.getTasksAfterSend().add(player::sendTransaction);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
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.WrapperPlayClientInteractEntity;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class ActionManager extends Check implements PacketCheck {
|
||||
private boolean attacking = false;
|
||||
private long lastAttack = 0;
|
||||
|
||||
public ActionManager(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(final PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
WrapperPlayClientInteractEntity action = new WrapperPlayClientInteractEntity(event);
|
||||
if (action.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
|
||||
player.totalFlyingPacketsSent = 0;
|
||||
attacking = true;
|
||||
lastAttack = System.currentTimeMillis();
|
||||
}
|
||||
} else if (isTickPacketIncludingNonMovement(event.getPacketType())) {
|
||||
player.totalFlyingPacketsSent++;
|
||||
attacking = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasAttackedSince(long time) {
|
||||
return System.currentTimeMillis() - lastAttack < time;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
|
||||
import com.github.retrooper.packetevents.protocol.item.ItemStack;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
public class AttackCooldownHandler extends Check implements PacketCheck {
|
||||
private int ticksSinceLastSwing;
|
||||
private ItemStack stack = ItemStack.EMPTY;
|
||||
// Since we don't know when the client ticks, we call updateHeldItem() when the held item changes,
|
||||
// but that means ticksSinceLastSwing is incremented after it gets reset. This is wrong, so we
|
||||
// compensate for that by not incrementing ticksSinceLastSwing if updateHeldItem() was called
|
||||
// (and the held item changed) before the tick packet.
|
||||
private boolean stackChanged;
|
||||
|
||||
public AttackCooldownHandler(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(final PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
|
||||
// FIXME: should only run when the click misses
|
||||
reset();
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
|
||||
if (packet.getAction() == DiggingAction.CANCELLED_DIGGING && packet.getBlockFace() == BlockFace.DOWN) {
|
||||
// FIXME: this could also be triggered by switching targets while looking at the bottom face of a block
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (isTickPacket(event.getPacketType())) {
|
||||
if (!stackChanged) {
|
||||
++ticksSinceLastSwing;
|
||||
}
|
||||
updateHeldItem();
|
||||
stackChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
ticksSinceLastSwing = 0;
|
||||
}
|
||||
|
||||
// called on client tick and whenever the slot gets updated
|
||||
public void updateHeldItem() {
|
||||
ItemStack held = player.inventory.getHeldItem().copy();
|
||||
|
||||
if (!(stack.isEmpty() && held.isEmpty() || stack.getType() == held.getType() && (stack.isDamageableItem() || stack.getLegacyData() == held.getLegacyData()))) {
|
||||
reset();
|
||||
stackChanged = true;
|
||||
}
|
||||
|
||||
stack = held;
|
||||
}
|
||||
|
||||
public float getMinimumProgress() {
|
||||
return GrimMath.clamp(((float) ticksSinceLastSwing + 0.5F) / (float) (1d / player.compensatedEntities.self.getAttributeValue(Attributes.ATTACK_SPEED) * 20.0D), 0, 1);
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.google.common.collect.ClassToInstanceMap;
|
||||
import com.google.common.collect.ImmutableClassToInstanceMap;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -76,7 +77,8 @@ public class CheckManager {
|
||||
private final ClassToInstanceMap<BlockBreakCheck> blockBreakChecks;
|
||||
private final ClassToInstanceMap<BlockPlaceCheck> blockPlaceChecks;
|
||||
private final ClassToInstanceMap<PostPredictionCheck> postPredictionChecks;
|
||||
private PacketEntityReplication packetEntityReplication = null;
|
||||
@Getter
|
||||
private final PacketEntityReplication packetEntityReplication;
|
||||
|
||||
private final List<PacketCheck> packetChecksValues;
|
||||
private final List<PositionCheck> positionChecksValues;
|
||||
@@ -88,16 +90,18 @@ public class CheckManager {
|
||||
private final List<PostPredictionCheck> postPredictionChecksValues;
|
||||
|
||||
public CheckManager(GrimPlayer player) {
|
||||
packetEntityReplication = new PacketEntityReplication(player);
|
||||
|
||||
packetChecks = new ImmutableClassToInstanceMap.Builder<PacketCheck>()
|
||||
.put(CompensatedCameraEntity.class, player.cameraEntity)
|
||||
.put(PacketOrderProcessor.class, player.packetOrderProcessor)
|
||||
.put(Reach.class, new Reach(player))
|
||||
.put(PacketEntityReplication.class, new PacketEntityReplication(player))
|
||||
.put(PacketEntityReplication.class, packetEntityReplication)
|
||||
.put(PacketChangeGameState.class, new PacketChangeGameState(player))
|
||||
.put(CompensatedInventory.class, player.inventory)
|
||||
.put(PacketPlayerAbilities.class, new PacketPlayerAbilities(player))
|
||||
.put(PacketWorldBorder.class, new PacketWorldBorder(player))
|
||||
.put(ActionManager.class, player.actionManager)
|
||||
.put(AttackCooldownHandler.class, player.attackCooldown)
|
||||
.put(TeamHandler.class, new TeamHandler(player))
|
||||
.put(ClientBrand.class, new ClientBrand(player))
|
||||
.put(NoFall.class, new NoFall(player))
|
||||
@@ -129,6 +133,7 @@ public class CheckManager {
|
||||
.put(BadPacketsV.class, new BadPacketsV(player))
|
||||
.put(BadPacketsY.class, new BadPacketsY(player))
|
||||
.put(BadPacketsZ.class, new BadPacketsZ(player))
|
||||
.put(SelfInteract.class, new SelfInteract(player))
|
||||
.put(InventoryA.class, new InventoryA(player))
|
||||
.put(InventoryB.class, new InventoryB(player))
|
||||
.put(InventoryE.class, new InventoryE(player))
|
||||
@@ -318,6 +323,21 @@ public class CheckManager {
|
||||
return (T) blockPlaceChecks.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketCheck> T getPacketCheck(Class<T> check) {
|
||||
return (T) packetChecks.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketCheck> T getPrePredictionCheck(Class<T> check) {
|
||||
return (T) prePredictionChecks.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PostPredictionCheck> T getPostPredictionCheck(Class<T> check) {
|
||||
return (T) postPredictionChecks.get(check);
|
||||
}
|
||||
|
||||
public void onPrePredictionReceivePacket(final PacketReceiveEvent packet) {
|
||||
for (PacketCheck check : prePredictionChecksValues) {
|
||||
check.onPacketReceive(packet);
|
||||
@@ -424,22 +444,6 @@ public class CheckManager {
|
||||
return getPostPredictionCheck(ExplosionHandler.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketCheck> T getPacketCheck(Class<T> check) {
|
||||
return (T) packetChecks.get(check);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketCheck> T getPrePredictionCheck(Class<T> check) {
|
||||
return (T) prePredictionChecks.get(check);
|
||||
}
|
||||
|
||||
public PacketEntityReplication getEntityReplication() {
|
||||
if (packetEntityReplication == null)
|
||||
packetEntityReplication = getPacketCheck(PacketEntityReplication.class);
|
||||
return packetEntityReplication;
|
||||
}
|
||||
|
||||
public NoFall getNoFall() {
|
||||
return getPacketCheck(NoFall.class);
|
||||
}
|
||||
@@ -464,15 +468,6 @@ public class CheckManager {
|
||||
return getPostPredictionCheck(DebugHandler.class);
|
||||
}
|
||||
|
||||
public OffsetHandler getOffsetHandler() {
|
||||
return getPostPredictionCheck(OffsetHandler.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PostPredictionCheck> T getPostPredictionCheck(Class<T> check) {
|
||||
return (T) postPredictionChecks.get(check);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (inited || initedAtomic.getAndSet(true)) return;
|
||||
inited = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.GrimUser;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.manager.init.ReloadableInitable;
|
||||
import ac.grim.grimac.manager.init.start.StartableInitable;
|
||||
@@ -8,7 +9,11 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.webhook.*;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.CompiledDiscordTemplate;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.Embed;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.EmbedField;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.EmbedFooter;
|
||||
import ac.grim.grimac.utils.data.webhook.discord.WebhookMessage;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -21,15 +26,19 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
private static final Predicate<String> WEBHOOK_REGEX = Pattern.compile("^https://discord\\.com/api(?:/v\\d+)?/webhooks/\\d+/[\\w-]+(\\?thread_id=\\d+)?$").asMatchPredicate();
|
||||
private static final Predicate<String> HTTPS_URL_REGEX = Pattern.compile("^https://[^/\\s]+/\\S+$").asMatchPredicate();
|
||||
private static final Duration timeout = Duration.ofSeconds(15);
|
||||
private static final HttpClient client = HttpClient.newBuilder().connectTimeout(timeout).build();
|
||||
private static final ConcurrentLinkedDeque<Pair<HttpRequest, CompletableFuture<Boolean>>> requests = new ConcurrentLinkedDeque<>();
|
||||
@@ -38,7 +47,8 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
private static long rateLimitedUntil;
|
||||
private URI url;
|
||||
private int embedColor;
|
||||
private String staticContent = "";
|
||||
private CompiledDiscordTemplate compiledContent;
|
||||
private char backtickReplacement = '\u02CB';
|
||||
private String embedTitle = "";
|
||||
private boolean includeTimestamp;
|
||||
private boolean includeVerbose;
|
||||
@@ -68,6 +78,9 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
@Override
|
||||
public void reload() {
|
||||
try {
|
||||
// Yes all of these fields should technically be volatile so they will be updated correctly on reload for HTTP threads to read
|
||||
// No we're not going to pay for atomic reads in the hot loop however cheap for a one in a billion chance to read an outdated config
|
||||
// When your discord webhook settings are changed (who changes them in prod?) that can be fixed with a restart
|
||||
ConfigManager config = GrimAPI.INSTANCE.getConfigManager().getConfig();
|
||||
if (!config.getBooleanElse("enabled", false)) {
|
||||
url = null;
|
||||
@@ -75,12 +88,29 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
}
|
||||
|
||||
String webhook = config.getStringElse("webhook", "");
|
||||
boolean strictValidation = !config.getBooleanElse("disable-webhook-validation", false);
|
||||
|
||||
if (!WEBHOOK_REGEX.test(webhook)) {
|
||||
LogUtil.error("Discord webhook url does not follow expected format (https://discord.com/api/webhooks/<id>/<token>): " + webhook);
|
||||
if (webhook.isEmpty()) {
|
||||
url = null;
|
||||
} else if (strictValidation) {
|
||||
if (!WEBHOOK_REGEX.test(webhook)) {
|
||||
LogUtil.error("Discord webhook URL does not match expected format"
|
||||
+ " (https://discord.com/api/webhooks/<id>/<token>): " + webhook);
|
||||
LogUtil.error("If you are using a proxy or custom endpoint,"
|
||||
+ " set 'disable-webhook-validation: true' in the Discord config.");
|
||||
url = null;
|
||||
} else {
|
||||
url = new URI(webhook);
|
||||
}
|
||||
} else {
|
||||
url = new URI(webhook);
|
||||
if (!HTTPS_URL_REGEX.test(webhook)) {
|
||||
LogUtil.error("Discord webhook URL is not a valid HTTPS URL: " + webhook);
|
||||
url = null;
|
||||
} else {
|
||||
LogUtil.info("Webhook validation disabled — using custom endpoint: "
|
||||
+ webhook.substring(0, Math.min(webhook.length(), 40)) + "...");
|
||||
url = new URI(webhook);
|
||||
}
|
||||
}
|
||||
// not adding these to the config since they may change in the future
|
||||
// mainly for just for allowing more customization
|
||||
@@ -100,9 +130,11 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
for (String string : config.getStringListElse("violation-content", getDefaultContents())) {
|
||||
sb.append(string).append("\n");
|
||||
}
|
||||
staticContent = sb.toString();
|
||||
includeTimestamp = config.getBooleanElse("include-timestamp", true);
|
||||
includeVerbose = config.getBooleanElse("include-verbose", true);
|
||||
String btReplace = config.getStringElse("backtick-replacement-char", "\u02CB");
|
||||
backtickReplacement = (btReplace.isEmpty()) ? '\u02CB' : btReplace.charAt(0);
|
||||
compiledContent = CompiledDiscordTemplate.compile(sb.toString());
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("Failed to load Discord webhook configuration", e);
|
||||
}
|
||||
@@ -111,25 +143,29 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private @NotNull @Unmodifiable List<@NotNull String> getDefaultContents() {
|
||||
return List.of(
|
||||
"**Player**: %player%",
|
||||
"**Player**: `%player%`",
|
||||
"**Check**: %check%",
|
||||
"**Violations**: %violations%",
|
||||
"**Client Version**: %version%",
|
||||
"**Brand**: %brand%",
|
||||
"**Brand**: `%brand%`",
|
||||
"**Ping**: %ping%",
|
||||
"**TPS**: %tps%"
|
||||
);
|
||||
}
|
||||
|
||||
public void sendAlert(GrimPlayer player, String verbose, String checkName, int violations) {
|
||||
public void sendAlert(@NotNull GrimPlayer player, String verbose, String checkName, int violations) {
|
||||
if (isDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String content = staticContent;
|
||||
content = content.replace("%check%", checkName.replace("_", "\\_")); // just in case any checks are added with an underscore
|
||||
content = content.replace("%violations%", Integer.toString(violations));
|
||||
content = MessageUtil.replacePlaceholders(player, content, true);
|
||||
// Per-alert overlay — avoids polluting the global static map
|
||||
Map<String, String> statics = new HashMap<>(GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements());
|
||||
statics.put("%check%", checkName);
|
||||
statics.put("%violations%", Integer.toString(violations));
|
||||
|
||||
Map<String, Function<GrimUser, String>> dynamics = GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements();
|
||||
|
||||
String content = compiledContent.render(player, statics, dynamics, backtickReplacement);
|
||||
|
||||
Embed embed = new Embed(content)
|
||||
.color(embedColor)
|
||||
@@ -137,14 +173,14 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
.imageURL(MessageUtil.replacePlaceholders(player, embedImageUrl, false))
|
||||
.thumbnailURL(MessageUtil.replacePlaceholders(player, embedThumbnailUrl, false))
|
||||
.footer(new EmbedFooter(
|
||||
MessageUtil.replacePlaceholders(player, embedFooterText, true),
|
||||
MessageUtil.replacePlaceholders(player, embedFooterText, false),
|
||||
MessageUtil.replacePlaceholders(player, embedFooterUrl, false)
|
||||
));
|
||||
|
||||
if (includeTimestamp) embed.timestamp(Instant.now());
|
||||
|
||||
if (!verbose.isEmpty() && includeVerbose) {
|
||||
embed.addFields(new EmbedField("Verbose", MessageUtil.filterDiscordText(verbose), true));
|
||||
embed.addFields(new EmbedField("Verbose", CompiledDiscordTemplate.escapeMarkdown(verbose), true));
|
||||
}
|
||||
|
||||
sendWebhookMessage(new WebhookMessage().addEmbeds(embed));
|
||||
|
||||
@@ -12,7 +12,7 @@ public abstract class AbstractTickEndEvent implements StartableInitable {
|
||||
}
|
||||
|
||||
protected void onEndOfTick(GrimPlayer player) {
|
||||
player.checkManager.getEntityReplication().onEndOfTickEvent();
|
||||
player.checkManager.getPacketEntityReplication().onEndOfTickEvent();
|
||||
}
|
||||
|
||||
protected boolean shouldInjectEndTick() {
|
||||
|
||||
+7
-7
@@ -2,6 +2,7 @@ package ac.grim.grimac.manager.player.handlers;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.handler.ResyncHandler;
|
||||
import ac.grim.grimac.platform.api.player.BlockTranslator;
|
||||
import ac.grim.grimac.platform.api.world.PlatformChunk;
|
||||
import ac.grim.grimac.platform.api.world.PlatformWorld;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
@@ -11,12 +12,8 @@ import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAcknowledgeBlockChanges;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultResyncHandler implements ResyncHandler {
|
||||
|
||||
private final GrimPlayer player;
|
||||
public record DefaultResyncHandler(GrimPlayer player) implements ResyncHandler {
|
||||
|
||||
private static void resyncPositions(GrimPlayer player, int minBlockX, int mY, int minBlockZ, int maxBlockX, int mxY, int maxBlockZ) {
|
||||
// Check the 4 corners of the player world for loaded chunks before calling event
|
||||
@@ -58,6 +55,8 @@ public class DefaultResyncHandler implements ResyncHandler {
|
||||
int minChunkZ = minBlockZ >> 4;
|
||||
int maxChunkZ = maxBlockZ >> 4;
|
||||
|
||||
BlockTranslator translator = player.platformPlayer.getBlockTranslator();
|
||||
|
||||
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
|
||||
int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
|
||||
int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
|
||||
@@ -81,8 +80,9 @@ public class DefaultResyncHandler implements ResyncHandler {
|
||||
for (int currZ = minZ; currZ <= maxZ; ++currZ) {
|
||||
for (int currX = minX; currX <= maxX; ++currX) {
|
||||
for (int currY = minY; currY <= maxY; ++currY) {
|
||||
int blockId = chunk.getBlockID(currX, currY | (currChunkY << 4), currZ);
|
||||
encodedBlocks[blockIndex++] = new WrapperPlayServerMultiBlockChange.EncodedBlock(blockId, currX, currY | (currChunkY << 4), currZ);
|
||||
int rawId = chunk.getBlockID(currX, currY | (currChunkY << 4), currZ);
|
||||
int networkId = translator.translate(rawId);
|
||||
encodedBlocks[blockIndex++] = new WrapperPlayServerMultiBlockChange.EncodedBlock(networkId, currX, currY | (currChunkY << 4), currZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ public class ResetTick implements Tickable {
|
||||
@Override
|
||||
public void tick() {
|
||||
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
|
||||
player.checkManager.getEntityReplication().tickStartTick();
|
||||
player.checkManager.getPacketEntityReplication().tickStartTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+19
-51
@@ -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);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package ac.grim.grimac.platform.api.player;
|
||||
|
||||
public interface BlockTranslator {
|
||||
BlockTranslator IDENTITY = serverBlockId -> serverBlockId;
|
||||
|
||||
int translate(int serverBlockId);
|
||||
}
|
||||
@@ -45,4 +45,6 @@ public interface PlatformPlayer extends GrimEntity, OfflinePlatformPlayer {
|
||||
* Vanilla MC replaces ServerPlayerEntity references on respawn and dimension change
|
||||
*/
|
||||
default void replaceNativePlayer(Object nativePlayerObject) {}
|
||||
|
||||
BlockTranslator getBlockTranslator();
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import ac.grim.grimac.platform.api.player.PlatformPlayer;
|
||||
import ac.grim.grimac.predictionengine.MovementCheckRunner;
|
||||
import ac.grim.grimac.predictionengine.PointThreeEstimator;
|
||||
import ac.grim.grimac.predictionengine.UncertaintyHandler;
|
||||
import ac.grim.grimac.manager.AttackCooldownHandler;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
|
||||
@@ -115,7 +116,7 @@ public class GrimPlayer implements GrimUser {
|
||||
// End transaction handling stuff
|
||||
// Manager like classes
|
||||
public final CheckManager checkManager;
|
||||
public final ActionManager actionManager;
|
||||
public final AttackCooldownHandler attackCooldown;
|
||||
public final PunishmentManager punishmentManager;
|
||||
public final MovementCheckRunner movementCheckRunner;
|
||||
public final SyncedTags tagManager;
|
||||
@@ -244,7 +245,6 @@ public class GrimPlayer implements GrimUser {
|
||||
public final Object2DoubleMap<FluidTag> fluidHeight = new Object2DoubleArrayMap<>(2);
|
||||
// possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+
|
||||
public final double[][] possibleEyeHeights = new double[3][];
|
||||
public int totalFlyingPacketsSent;
|
||||
public boolean hasInventoryOpen;
|
||||
public long lastInventoryOpen;
|
||||
public InventoryDesyncStatus inventoryDesyncStatus;
|
||||
@@ -296,7 +296,7 @@ public class GrimPlayer implements GrimUser {
|
||||
cameraEntity = new CompensatedCameraEntity(this);
|
||||
|
||||
lastInstanceManager = new LastInstanceManager(this);
|
||||
actionManager = new ActionManager(this);
|
||||
attackCooldown = new AttackCooldownHandler(this);
|
||||
checkManager = new CheckManager(this);
|
||||
punishmentManager = new PunishmentManager(this);
|
||||
this.tagManager = new SyncedTags(this); // must be after this.user = user
|
||||
@@ -414,7 +414,7 @@ public class GrimPlayer implements GrimUser {
|
||||
if (viaPacketTracker != null) viaPacketTracker.setIntervalPackets(viaPacketTracker.getIntervalPackets() - 1);
|
||||
|
||||
if (skipped > 0 && System.currentTimeMillis() - joinTime > 5000)
|
||||
checkManager.getCheck(TransactionOrder.class).flagAndAlert("skipped: " + skipped);
|
||||
checkManager.getCheck(TransactionOrder.class).flagAndAlert("skipped=" + skipped);
|
||||
|
||||
do {
|
||||
data = transactionsSent.poll();
|
||||
|
||||
+2
-2
@@ -273,13 +273,13 @@ public class PredictionEngine {
|
||||
}
|
||||
|
||||
private Pair<Vector3dm, Vector3dm> doSeekingWallCollisions(GrimPlayer player, Vector3dm primaryPushMovement, Vector3dm originalClientVel, VectorData clientVelAfterInput) {
|
||||
// TODO: causes falses when riding nautilus on the ground, figure out why this is even here
|
||||
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vector.getY() == 0;
|
||||
// Extra collision epsilon required for vehicles to be accurate
|
||||
double xAdditional = Math.signum(primaryPushMovement.getX()) * SimpleCollisionBox.COLLISION_EPSILON;
|
||||
// The server likes sending y=0 kb "lifting" the player off the ground.
|
||||
// The client doesn't send the vehicles onGround status, so we can't check for ground like normal.
|
||||
double yAdditional = vehicleKB ? 0 : (primaryPushMovement.getY() > 0 ? 1 : -1) * SimpleCollisionBox.COLLISION_EPSILON;
|
||||
double yAdditional = vehicleKB ? 0 : (player.inVehicle() ? Math.signum(primaryPushMovement.getY()) : (primaryPushMovement.getY() > 0 ? 1 : -1))
|
||||
* SimpleCollisionBox.COLLISION_EPSILON;
|
||||
double zAdditional = Math.signum(primaryPushMovement.getZ()) * SimpleCollisionBox.COLLISION_EPSILON;
|
||||
|
||||
// Expand by the collision epsilon to test if the player collided with a block (as this resets the velocity in that direction)
|
||||
|
||||
-2
@@ -17,8 +17,6 @@ public class PredictionEngineNautilusWater extends PredictionEngine {
|
||||
|
||||
@Override
|
||||
public void endOfTick(GrimPlayer player, double delta) {
|
||||
super.endOfTick(player, delta);
|
||||
|
||||
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
|
||||
vector.vector.setX(vector.vector.getX() * multiplier);
|
||||
vector.vector.setY(vector.vector.getY() * multiplier);
|
||||
|
||||
@@ -5,6 +5,8 @@ import ac.grim.grimac.api.GrimUser;
|
||||
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;
|
||||
@@ -26,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;
|
||||
}
|
||||
|
||||
@@ -34,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);
|
||||
@@ -60,9 +66,9 @@ public class MessageUtil {
|
||||
private @Nullable String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @Nullable String string, boolean removeFormatting) {
|
||||
if (string == null) return null;
|
||||
|
||||
// --- OPTIMIZATION 1: THE FAST PATH ---
|
||||
// If the string contains no '%' characters, it's impossible for it to have placeholders.
|
||||
// indexOf() is a JVM intrinsic and is magnitudes faster than even creating a Matcher.
|
||||
// --- PHASE 1: FAST PATH ---
|
||||
// JVM Intrinsic: Scanning for a char is significantly faster than initializing a Regex Matcher.
|
||||
// If there is no '%', we can skip all allocation.
|
||||
if (string.indexOf('%') == -1) {
|
||||
// Since there are no % signs we can skip calling papi or our own replacement code
|
||||
return string;
|
||||
@@ -70,8 +76,8 @@ public class MessageUtil {
|
||||
|
||||
final Matcher matcher = UNIFIED_PLACEHOLDER_PATTERN.matcher(string);
|
||||
|
||||
// If matcher.find() is false, it means '%' existed but not in a valid %...% pattern.
|
||||
// This avoids allocating a StringBuilder unless absolutely necessary.
|
||||
// If '%' exists but doesn't form a valid simple placeholder, skip to the PAPI/Legacy handler.
|
||||
// This avoids allocating the StringBuilder below.
|
||||
if (!matcher.find()) {
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string);
|
||||
}
|
||||
@@ -79,17 +85,17 @@ public class MessageUtil {
|
||||
// Get references to the maps once, outside the loop.
|
||||
final Map<String, String> staticReplacements = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements();
|
||||
final Map<String, Function<GrimUser, String>> variableReplacements = GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements();
|
||||
final StringBuilder sb = new StringBuilder(string.length() + 32); // Pre-size with a little extra room
|
||||
// 32 is a heuristic buffer. It roughly covers the expansion cost of one UUID (36 chars) vs one placeholder (6 chars).
|
||||
final StringBuilder sb = new StringBuilder(string.length() + 32);
|
||||
|
||||
// --- OPTIMIZATION 2: THE UNIFIED SINGLE-PASS LOOP ---
|
||||
// We use a do-while loop because we already performed the first matcher.find().
|
||||
// --- PHASE 2: THE REPLACEMENT LOOP ---
|
||||
// Used do-while because the first `matcher.find()` was already called above.
|
||||
do {
|
||||
// The full placeholder, e.g., "%tps%" or "%prefix%"
|
||||
final String keyWithPercent = matcher.group(0);
|
||||
String value = null;
|
||||
|
||||
// --- OPTIMIZATION 3: UNIFIED LOOKUP ---
|
||||
// We check the static map first. This is a single, O(1) hash map lookup.
|
||||
// optimization: We check the static map first. This is a single, O(1) hash map lookup.
|
||||
String staticValue = staticReplacements.get(keyWithPercent);
|
||||
|
||||
if (staticValue != null) {
|
||||
@@ -109,15 +115,13 @@ public class MessageUtil {
|
||||
// In that case, we treat the placeholder as literal text by appending the original key.
|
||||
if (value == null) {
|
||||
value = keyWithPercent;
|
||||
}
|
||||
|
||||
// --- OPTIMIZATION 4: CONDITIONAL FORMATTING ---
|
||||
// The check for `removeFormatting` is inside the loop, but it's a simple boolean
|
||||
// yes the check for `removeFormatting` is inside the loop, but it's a simple boolean
|
||||
// check and the cost is negligible compared to the string operations.
|
||||
if (removeFormatting) {
|
||||
// Note: This assumes `filterDiscordText` is reasonably fast.
|
||||
// Do NOT escape - this is probably a PAPI key that must remain intact
|
||||
} else if (removeFormatting) {
|
||||
// Note: This assumes `escapeMarkdown` is reasonably fast.
|
||||
// If it's slow, there are further micro-optimizations, but this is the right place for it.
|
||||
value = filterDiscordText(value);
|
||||
value = CompiledDiscordTemplate.escapeMarkdown(value);
|
||||
}
|
||||
|
||||
// `appendReplacement` efficiently appends the text between matches and our replacement value.
|
||||
@@ -135,35 +139,6 @@ public class MessageUtil {
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, grimReplaced).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
|
||||
}
|
||||
|
||||
public static String filterDiscordText(String message) {
|
||||
if (message == null || message.isBlank()) return message;
|
||||
final StringBuilder sb = new StringBuilder(message.length());
|
||||
for (int i = 0; i < message.length(); ++i) {
|
||||
final char c = message.charAt(i);
|
||||
// Escape a newline
|
||||
if (c == '\n') {
|
||||
sb.append("\\n");
|
||||
} // Escape Markdown special characters
|
||||
else if (c == '`' || c == '*' || c == '_' || c == '~' || c == '|') {
|
||||
sb.append('\\').append(c);
|
||||
} else {
|
||||
// Escape "# ", "> ", etc
|
||||
if (c == '#' || c == '>' || c == '-') {
|
||||
// check if there's a space next
|
||||
if (((i + 1 < message.length()) && (message.charAt(i + 1) == ' '))
|
||||
&& ((i == 0) || (message.charAt(i - 1) == '\n'))) {
|
||||
sb.append("\\").append(c);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public @NotNull Component replacePlaceholders(@NotNull GrimPlayer player, @NotNull Component component) {
|
||||
// Replacement config that forces any placeholder replacement to be pure text
|
||||
final TextReplacementConfig safeReplacement = TextReplacementConfig.builder()
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -587,7 +587,7 @@ public enum HitboxData implements HitBoxFactory {
|
||||
new HexCollisionBox(0.0D, 0.0D, 14.0D, 2.0D, 16.0D, 16.0D),
|
||||
new HexCollisionBox(14.0D, 0.0D, 14.0D, 16.0D, 16.0D, 16.0D));
|
||||
|
||||
if (data.getHalf() == Half.LOWER) { // Add the unstable shape to the collision boxes
|
||||
if (data.isBottom()) { // Add the unstable shape to the collision boxes
|
||||
box.add(new HexCollisionBox(0.0D, 0.0D, 0.0D, 2.0D, 2.0D, 16.0D));
|
||||
box.add(new HexCollisionBox(14.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D));
|
||||
box.add(new HexCollisionBox(0.0D, 0.0D, 14.0D, 16.0D, 2.0D, 16.0D));
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.json;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ public class PacketEntitySelf extends PacketEntity {
|
||||
trackAttribute(movementSpeed);
|
||||
trackAttribute(ValuedAttribute.ranged(Attributes.ATTACK_DAMAGE, 2, 0, 2048)); // NOTE: Not synced to client currently.
|
||||
trackAttribute(ValuedAttribute.ranged(Attributes.ATTACK_SPEED, 4, 0, 1024)
|
||||
.requiredVersion(player, ClientVersion.V_1_9));
|
||||
.requiredVersion(player, ClientVersion.V_1_9)
|
||||
.withGetRewriter(value -> PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_9) ? 20 : value));
|
||||
trackAttribute(ValuedAttribute.ranged(Attributes.JUMP_STRENGTH, 0.42f, 0, 32)
|
||||
.requiredVersion(player, ClientVersion.V_1_20_5));
|
||||
trackAttribute(ValuedAttribute.ranged(Attributes.BLOCK_BREAK_SPEED, 1.0, 0, 1024)
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.GrimUser;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public record CompiledDiscordTemplate(Segment[] segments) {
|
||||
|
||||
/**
|
||||
* Markdown context as determined by a state-machine scan of the template.
|
||||
* Used only at compile time.
|
||||
*/
|
||||
private enum MarkdownContext {
|
||||
NORMAL,
|
||||
INLINE_CODE,
|
||||
CODE_BLOCK
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape strategy assigned to each placeholder at compile time.
|
||||
*/
|
||||
public enum EscapeMode {
|
||||
/**
|
||||
* Full Markdown escaping: \, `, *, _, ~, |, [], (), line-start chars, newlines
|
||||
*/
|
||||
FULL_MARKDOWN,
|
||||
/**
|
||||
* Inside a code span: only backtick substitution (backslash escaping doesn't work in Discord code spans)
|
||||
*/
|
||||
CODE_SPAN,
|
||||
/**
|
||||
* No escaping (placeholder left raw for PAPI)
|
||||
*/
|
||||
NONE
|
||||
}
|
||||
|
||||
// Identical to the existing pattern
|
||||
private static final Pattern PLACEHOLDER = Pattern.compile("%([a-zA-Z0-9_]+)%");
|
||||
|
||||
sealed interface Segment permits Literal, Placeholder {
|
||||
}
|
||||
|
||||
record Literal(String text) implements Segment {
|
||||
}
|
||||
|
||||
record Placeholder(String key, EscapeMode mode) implements Segment {
|
||||
}
|
||||
|
||||
// ──────────────────── COMPILE (once per config reload) ────────────────────
|
||||
public static CompiledDiscordTemplate compile(String template) {
|
||||
List<Segment> parts = new ArrayList<>();
|
||||
Matcher m = PLACEHOLDER.matcher(template);
|
||||
MarkdownContext ctx = MarkdownContext.NORMAL;
|
||||
int lastEnd = 0;
|
||||
|
||||
while (m.find()) {
|
||||
String gap = template.substring(lastEnd, m.start());
|
||||
if (!gap.isEmpty()) parts.add(new Literal(gap));
|
||||
|
||||
// Advance the context through all the literal text before this placeholder
|
||||
ctx = advanceContext(ctx, gap);
|
||||
|
||||
EscapeMode mode = switch (ctx) {
|
||||
case NORMAL -> EscapeMode.FULL_MARKDOWN;
|
||||
case INLINE_CODE,
|
||||
CODE_BLOCK -> EscapeMode.CODE_SPAN;
|
||||
};
|
||||
parts.add(new Placeholder(m.group(0), mode));
|
||||
lastEnd = m.end();
|
||||
}
|
||||
|
||||
if (lastEnd < template.length()) {
|
||||
parts.add(new Literal(template.substring(lastEnd)));
|
||||
}
|
||||
return new CompiledDiscordTemplate(parts.toArray(Segment[]::new));
|
||||
}
|
||||
|
||||
// ──────────────────── RENDER (once per alert) ────────────────────
|
||||
/**
|
||||
* @param player Grim player (nullable)
|
||||
* @param statics Static replacements (may include per-alert overrides like %check%)
|
||||
* @param dynamics Dynamic replacements (lazy functions like %tps%)
|
||||
* @param backtickReplacement Char to substitute for backticks inside code spans (loaded from config)
|
||||
*/
|
||||
public String render(@NotNull GrimPlayer player,
|
||||
@NotNull Map<String, String> statics,
|
||||
@NotNull Map<String, Function<GrimUser, String>> dynamics,
|
||||
char backtickReplacement) {
|
||||
StringBuilder sb = new StringBuilder(segments.length * 32);
|
||||
for (Segment seg : segments) {
|
||||
if (seg instanceof Literal l) {
|
||||
sb.append(l.text);
|
||||
} else if (seg instanceof Placeholder p) {
|
||||
// Priority: static → dynamic → external (PAPI)
|
||||
String val = statics.get(p.key);
|
||||
|
||||
if (val == null) {
|
||||
Function<GrimUser, String> fn = dynamics.get(p.key);
|
||||
if (fn != null) val = fn.apply(player);
|
||||
}
|
||||
|
||||
if (val == null) {
|
||||
String resolved = GrimAPI.INSTANCE.getMessagePlaceHolderManager()
|
||||
.replacePlaceholders(player.platformPlayer, p.key);
|
||||
if (!resolved.equals(p.key)) val = resolved;
|
||||
}
|
||||
|
||||
if (val != null) {
|
||||
sb.append(escape(val, p.mode, backtickReplacement));
|
||||
} else {
|
||||
sb.append(p.key); // truly unresolved — leave raw
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String escape(String value, EscapeMode mode, char backtickReplacement) {
|
||||
if (mode == EscapeMode.NONE) return value;
|
||||
if (mode == EscapeMode.CODE_SPAN) return escapeCodeSpan(value, backtickReplacement);
|
||||
return escapeMarkdown(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full Discord Markdown escaping for untrusted data values.
|
||||
* <p>
|
||||
* This method treats input as <b>raw data</b>, not pre-formatted markdown.
|
||||
* A literal backslash in a player name is data and must render as a visible backslash.
|
||||
* This means "already escaped" input like {@code \*} correctly becomes {@code \\*}
|
||||
* (rendering as {@code \*}), which is the intended behavior — identical to SQL parameterization.
|
||||
* <p>
|
||||
* All escaped characters ({@code \X}) render identically to their unescaped form ({@code X})
|
||||
* in Discord's CommonMark variant, so escaping is invisible to end users.
|
||||
*/
|
||||
public static String escapeMarkdown(String s) {
|
||||
if (s == null || s.isEmpty()) return s;
|
||||
StringBuilder sb = new StringBuilder(s.length() + 16);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
// Backslash MUST be first — prevents our own escape backslashes
|
||||
// from being re-escaped if the input already contains backslashes.
|
||||
case '\\' -> sb.append("\\\\");
|
||||
// Inline code spans (`text`)
|
||||
case '`' -> sb.append("\\`");
|
||||
// Bold (**text**) and italic (*text*)
|
||||
case '*' -> sb.append("\\*");
|
||||
// Underlined (__text__) and italic (_text_)
|
||||
case '_' -> sb.append("\\_");
|
||||
// Strikethrough (~~text~~)
|
||||
case '~' -> sb.append("\\~");
|
||||
// Spoiler tags (||text||)
|
||||
case '|' -> sb.append("\\|");
|
||||
// Link [text](url) and image  syntax
|
||||
case '[' -> sb.append("\\[");
|
||||
case ']' -> sb.append("\\]");
|
||||
case '(' -> sb.append("\\(");
|
||||
case ')' -> sb.append("\\)");
|
||||
// Auto-linking (https://...) and emoji (:name:)
|
||||
case ':' -> sb.append("\\:");
|
||||
// Timestamps (<t:...>), mentions (<@id>), custom emoji (<:n:id>),
|
||||
// embed suppression (<url>)
|
||||
case '<' -> sb.append("\\<");
|
||||
// Headers (#, ##, ###), block quotes (>, >>>).
|
||||
// Always escaped — \# and \> render identically to # and >.
|
||||
case '#' -> sb.append("\\#");
|
||||
case '>' -> sb.append("\\>");
|
||||
// Unordered lists (- item) and subtext (-# text)
|
||||
case '-' -> sb.append("\\-");
|
||||
// Ordered lists (1. item). \. renders identically to .
|
||||
case '.' -> sb.append("\\.");
|
||||
// Newlines in injected values would break embed layout and enable
|
||||
// line-start syntax injection (headers, quotes, lists).
|
||||
// Replaced with literal "\n" text. Template newlines are unaffected
|
||||
// (they pass through as Literal segments, not through this method).
|
||||
case '\n' -> sb.append("\\n");
|
||||
default -> sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Code span escaping: Discord ignores backslash escapes inside code spans,
|
||||
* so backticks are replaced with a visually-similar substitute character.
|
||||
* If replacement == '`', this is a no-op (user disabled the feature).
|
||||
*/
|
||||
public static String escapeCodeSpan(String s, char replacement) {
|
||||
if (s == null || s.isEmpty() || replacement == '`') return s;
|
||||
return s.replace('`', replacement);
|
||||
}
|
||||
|
||||
// ──────────────────── STATE MACHINE ────────────────────
|
||||
|
||||
/**
|
||||
* Advances Markdown context through literal template text.
|
||||
* Handles: backslash escapes (NORMAL only), single-` inline code, triple-` code blocks.
|
||||
* <p>
|
||||
* Edge cases resolved:
|
||||
* <pre>
|
||||
* `text %p% text` → INLINE_CODE (` opens before placeholder)
|
||||
* `a`%p%`b` → NORMAL (` opens then ` closes before placeholder)
|
||||
* \`%p%` → NORMAL (backtick is backslash-escaped)
|
||||
* </pre>
|
||||
* <b>Known limitation:</b> Double-backtick code spans ({@code `` text ``}) are not tracked.
|
||||
*/
|
||||
private static MarkdownContext advanceContext(MarkdownContext ctx, String text) {
|
||||
int i = 0;
|
||||
while (i < text.length()) {
|
||||
char c = text.charAt(i);
|
||||
if (ctx == MarkdownContext.NORMAL) {
|
||||
if (c == '\\' && i + 1 < text.length()) {
|
||||
i += 2; // skip escaped char entirely
|
||||
continue;
|
||||
}
|
||||
if (c == '`') {
|
||||
if (i + 2 < text.length()
|
||||
&& text.charAt(i + 1) == '`'
|
||||
&& text.charAt(i + 2) == '`') {
|
||||
ctx = MarkdownContext.CODE_BLOCK;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
ctx = MarkdownContext.INLINE_CODE;
|
||||
}
|
||||
} else if (ctx == MarkdownContext.INLINE_CODE) {
|
||||
if (c == '`') ctx = MarkdownContext.NORMAL;
|
||||
} else { // CODE_BLOCK
|
||||
if (c == '`'
|
||||
&& i + 2 < text.length()
|
||||
&& text.charAt(i + 1) == '`'
|
||||
&& text.charAt(i + 2) == '`') {
|
||||
ctx = MarkdownContext.NORMAL;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.utils.data.json.JsonSerializable;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
@@ -12,8 +13,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ac.grim.grimac.utils.webhook.JsonSerializable.deserializeArray;
|
||||
import static ac.grim.grimac.utils.webhook.JsonSerializable.serializeArray;
|
||||
import static ac.grim.grimac.utils.data.json.JsonSerializable.deserializeArray;
|
||||
import static ac.grim.grimac.utils.data.json.JsonSerializable.serializeArray;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@Getter
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.utils.data.json.JsonSerializable;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.utils.data.json.JsonSerializable;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.utils.data.json.JsonSerializable;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.webhook;
|
||||
package ac.grim.grimac.utils.data.webhook.discord;
|
||||
|
||||
import ac.grim.grimac.utils.data.json.JsonSerializable;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
@@ -11,8 +12,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static ac.grim.grimac.utils.webhook.JsonSerializable.deserializeArray;
|
||||
import static ac.grim.grimac.utils.webhook.JsonSerializable.serializeArray;
|
||||
import static ac.grim.grimac.utils.data.json.JsonSerializable.deserializeArray;
|
||||
import static ac.grim.grimac.utils.data.json.JsonSerializable.serializeArray;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -21,7 +21,8 @@ public class Inventory extends AbstractContainerMenu {
|
||||
public static final int SLOT_LEGGINGS = 6;
|
||||
public static final int SLOT_BOOTS = 7;
|
||||
private static final int TOTAL_SIZE = 46;
|
||||
public int selected = 0;
|
||||
@Getter
|
||||
private int selected;
|
||||
@Getter
|
||||
private final CorrectingPlayerInventoryStorage inventoryStorage;
|
||||
|
||||
@@ -68,7 +69,7 @@ public class Inventory extends AbstractContainerMenu {
|
||||
}
|
||||
|
||||
public boolean hasItemType(ItemType item) {
|
||||
for (int i = 0; i < inventoryStorage.items.length; ++i) {
|
||||
for (int i = 0; i < inventoryStorage.getSize(); ++i) {
|
||||
if (inventoryStorage.getItem(i).getType() == item) {
|
||||
return true;
|
||||
}
|
||||
@@ -93,7 +94,7 @@ public class Inventory extends AbstractContainerMenu {
|
||||
}
|
||||
|
||||
public int getFreeSlot() {
|
||||
for (int i = 0; i < inventoryStorage.items.length; ++i) {
|
||||
for (int i = 0; i < inventoryStorage.getSize(); ++i) {
|
||||
if (inventoryStorage.getItem(i).isEmpty()) {
|
||||
return i;
|
||||
}
|
||||
@@ -252,4 +253,9 @@ public class Inventory extends AbstractContainerMenu {
|
||||
public boolean canTakeItemForPickAll(ItemStack p_38908_, Slot p_38909_) {
|
||||
return p_38909_.inventoryStorageSlot != 0; // Result slot
|
||||
}
|
||||
|
||||
public void setSelected(int selected) {
|
||||
this.selected = selected;
|
||||
this.player.attackCooldown.updateHeldItem();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import com.github.retrooper.packetevents.protocol.item.ItemStack;
|
||||
import lombok.Getter;
|
||||
|
||||
public class InventoryStorage {
|
||||
protected final ItemStack[] items;
|
||||
private final ItemStack[] items;
|
||||
@Getter
|
||||
private final int size;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class InventoryStorage {
|
||||
}
|
||||
|
||||
public ItemStack removeItem(int slot, int amount) {
|
||||
return slot >= 0 && slot < items.length && !items[slot].isEmpty() && amount > 0 ? items[slot].split(amount) : ItemStack.EMPTY;
|
||||
return slot >= 0 && slot < size && !items[slot].isEmpty() && amount > 0 ? items[slot].split(amount) : ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
public int getMaxStackSize() {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -56,6 +56,14 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
// Unsupported inventory is -2
|
||||
private int packetSendingInventorySize = PLAYER_INVENTORY_CASE;
|
||||
|
||||
// The item held at the start of the current client tick (processed at the end of the previous tick)
|
||||
// Currently only used by 1.21.11+ players to handle attribute swapping items with the ATTACK_RANGE Component
|
||||
private ItemStack startOfTickStack = ItemStack.EMPTY;
|
||||
|
||||
public ItemStack getStartOfTickStack() {
|
||||
return startOfTickStack;
|
||||
}
|
||||
|
||||
public CompensatedInventory(GrimPlayer playerData) {
|
||||
super(playerData);
|
||||
|
||||
@@ -223,7 +231,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
return;
|
||||
|
||||
// 1.19.4+ clients support swapping with non-empty items
|
||||
int swapItemSlot = item.getHand() == InteractionHand.MAIN_HAND ? inventory.selected + Inventory.HOTBAR_OFFSET : Inventory.SLOT_OFFHAND;
|
||||
int swapItemSlot = item.getHand() == InteractionHand.MAIN_HAND ? inventory.getSelected() + Inventory.HOTBAR_OFFSET : Inventory.SLOT_OFFHAND;
|
||||
|
||||
// Mojang implemented this stupidly, I rewrote their item swap code to make it somewhat cleaner.
|
||||
// Slot in hotbar
|
||||
@@ -234,9 +242,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
inventory.getInventoryStorage().handleClientClaimedSlotSet(slot);
|
||||
inventory.getInventoryStorage().setItem(slot, use);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
|
||||
WrapperPlayClientPlayerDigging dig = new WrapperPlayClientPlayerDigging(event);
|
||||
|
||||
// 1.8 clients don't predict dropping items
|
||||
@@ -258,18 +264,14 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
inventory.setHeldItem(null);
|
||||
inventory.getInventoryStorage().handleClientClaimedSlotSet(Inventory.HOTBAR_OFFSET + player.packetStateData.lastSlotSelected);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
|
||||
final int slot = new WrapperPlayClientHeldItemChange(event).getSlot();
|
||||
|
||||
// Stop people from spamming the server with an out-of-bounds exception
|
||||
if (slot > 8 || slot < 0) return;
|
||||
|
||||
inventory.selected = slot;
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CREATIVE_INVENTORY_ACTION) {
|
||||
inventory.setSelected(slot);
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CREATIVE_INVENTORY_ACTION) {
|
||||
WrapperPlayClientCreativeInventoryAction action = new WrapperPlayClientCreativeInventoryAction(event);
|
||||
if (player.gamemode != GameMode.CREATIVE) return;
|
||||
|
||||
@@ -281,9 +283,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
inventory.getSlot(action.getSlot()).set(action.getItemStack());
|
||||
inventory.getInventoryStorage().handleClientClaimedSlotSet(action.getSlot());
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW && !event.isCancelled()) {
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW && !event.isCancelled()) {
|
||||
WrapperPlayClientClickWindow click = new WrapperPlayClientClickWindow(event);
|
||||
|
||||
// How is this possible? Maybe transaction splitting.
|
||||
@@ -312,10 +312,10 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
if (slot == -1 || slot == -999 || slot < menu.getSlots().size()) {
|
||||
menu.doClick(button, slot, clickType);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
this.closeActiveInventory();
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
|
||||
this.startOfTickStack = getHeldItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,10 @@ public class CorrectingPlayerInventoryStorage extends InventoryStorage {
|
||||
}
|
||||
|
||||
super.setItem(item, stack);
|
||||
|
||||
if (item == player.inventory.inventory.getSelected() + Inventory.HOTBAR_OFFSET) {
|
||||
player.attackCooldown.updateHeldItem();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -233,4 +233,8 @@ public class GrimMath {
|
||||
public static double square(double num) {
|
||||
return num * num;
|
||||
}
|
||||
|
||||
public static double distanceSquared(double x1, double y1, double z1, double x2, double y2, double z2) {
|
||||
return GrimMath.square(x1 - x2) + GrimMath.square(y1 - y2) + GrimMath.square(z1 - z2);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -263,8 +263,8 @@ public final class BoundingBoxSize {
|
||||
// WHY DOES VIAVERSION OFFSET BOATS? THIS MAKES IT HARD TO SUPPORT, EVEN IF WE INTERPOLATE RIGHT.
|
||||
// I gave up and just exempted boats from the reach check and gave up with interpolation for collisions
|
||||
return 0.5625f;
|
||||
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.NAUTILUS)) {
|
||||
return 0.95F;
|
||||
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.ABSTRACT_NAUTILUS)) {
|
||||
return 0.95f;
|
||||
} else if (type == EntityTypes.HAPPY_GHAST) {
|
||||
return 4.0f;
|
||||
} else if (type == EntityTypes.CAT) {
|
||||
|
||||
@@ -232,7 +232,7 @@ public class WorldRayTrace {
|
||||
// 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) {
|
||||
double x, double y, double z) {
|
||||
HitData firstObstruction = null;
|
||||
double firstObstructionDistanceSq = 0;
|
||||
|
||||
@@ -241,7 +241,7 @@ public class WorldRayTrace {
|
||||
Vector3dm lookVec = vectorDoublePair.first();
|
||||
double eye = vectorDoublePair.second();
|
||||
|
||||
Vector3dm eyes = new Vector3dm(from.getX(), from.getY() + eye, from.getZ());
|
||||
Vector3dm eyes = new Vector3dm(x, y + eye, z);
|
||||
// this function is completely 0.03 aware
|
||||
final HitData hitResult = WorldRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -24,6 +24,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -25,6 +25,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
# GrimAC główna konfiguracja
|
||||
# Kod źródłowy: https://github.com/MWHunter/Grim
|
||||
# Prawa autorskie 2025 DefineOutside i współautorzy, licencjonowane na podstawie GPLv3.
|
||||
# Zmodyfikowane pliki binarne, albo pluginy z skopiowanym kodem grim, muszą być prywatne,
|
||||
# lub z pełnym kodem źródłowym dostępnym dla kupujących bez dodatkowych kosztów.
|
||||
|
||||
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.
|
||||
# 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?
|
||||
send: false
|
||||
# Czy alerty otrzymywane z innych serwerów powinny być przekazywane temu serwerowi?
|
||||
receive: false
|
||||
|
||||
verbose:
|
||||
print-to-console: false
|
||||
|
||||
check-for-updates: true
|
||||
|
||||
client-brand:
|
||||
# Oznacza to, że nie będzie transmitować swojej marki operatorom, jeśli marka będzie pasować do następujących wyrażeń regularnych.
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
# Grim umieści na czarnej liście konkretne wersje Forge, które zawierają wbudowane modyfikacje Reach (Forge 1.18.2 do 1.19.3).
|
||||
# Ustawienie tej opcji na fałsz umożliwi tym klientom połączenie się z serwerem. Wyłączasz tę opcję na własne ryzyko.
|
||||
disconnect-blacklisted-forge-versions: true
|
||||
|
||||
spectators:
|
||||
# Ukryj wszystkich widzów z uprawnieniem grim.spectator, niezależnie od tego, czy są oni faktycznie aktywnymi widzami
|
||||
hide-regardless: false
|
||||
# Spowoduje, że widzowie będą ukryci tylko w tych światach, a pozostawienie pustego pola umożliwi dostęp do wszystkich światów
|
||||
allowed-worlds:
|
||||
- ""
|
||||
|
||||
# Ile czasu powinni mieć gracze, zanim ich wyrzucimy za przekroczenie limitu czasu? Domyślnie = 60 sekund
|
||||
max-transaction-time: 60
|
||||
|
||||
# Włącz tę opcję, aby uniemożliwić Grimowi anulowanie komunikatów Pong otrzymanych od graczy.
|
||||
# Może to poprawić kompatybilność z innymi programami antycheatowymi. Może to powodować problemy z limiterami pakietów.
|
||||
# Nie włączaj tej opcji, jeśli nie masz pewności, co robisz.
|
||||
disable-pong-cancelling: false
|
||||
|
||||
# Czy domyślna procedura obsługi resynchronizacji bloków powinna zostać wyłączona?
|
||||
# Domyślnie Grim odczytuje bloki ze świata serwera i wysyła je do klienta w celu naprawy desynchronizacji.
|
||||
# Jeśli używasz wtyczek, które tworzą fałszywe bloki oparte na pakietach (które nie istnieją w świecie serwera),
|
||||
# powinieneś ustawić tę opcję na true, aby uniemożliwić Grimowi nadpisywanie ich blokami air/real.
|
||||
# Wymusza to użycie procedury obsługi „NoOp” (No Operation), chyba że zostanie ona udostępniona za pośrednictwem API.
|
||||
disable-default-resync-handler: false
|
||||
|
||||
# Czy zduplikowany pakiet ruchu powinien zostać anulowany?
|
||||
# Mojang naprawił ten problem w wersji 1.21. To była ich próba naprawienia „desynchronizacji kontenera”. https://bugs.mojang.com/browse/MC-12363
|
||||
# To ustawienie dotyczy tylko klientów 1.17-1.20.5 na serwerach 1.8.
|
||||
cancel-duplicate-packet: true
|
||||
|
||||
# Czy ignorować rotację w zduplikowanych pakietach
|
||||
ignore-duplicate-packet-rotation: false
|
||||
|
||||
Simulation:
|
||||
# O ile powinniśmy pomnożyć całkowitą przewagę, gdy gracz gra zgodnie z zasadami?
|
||||
# Tak wygląda domyślna konfiguracja (oś x = sekundy, oś y = 1/1000 bloku): https://www.desmos.com/calculator/d4ufgxrxer
|
||||
setback-decay-multiplier: 0.999
|
||||
# Jak duże odchylenie od ruchu gracza powinno skutkować naruszeniem?
|
||||
# Mierzone w blokach od możliwego ruchu.
|
||||
# Uwzględniamy Optifine, przełączając tablice trygonometryczne, ale zmniejszenie tej wartości do 0.001 zmniejszy
|
||||
# liczbę fałszywych alarmów spowodowanych przez FastMath, jeśli ta kompensacja nie zadziała...
|
||||
threshold: 0.001
|
||||
# Jak duże naruszenie w jednym ticku spowoduje natychmiastowe cofnięcie gracza?
|
||||
# -1 aby wyłączyć
|
||||
immediate-setback-threshold: 0.1
|
||||
# Jak dużą całkowitą przewagę (we wszystkich tickach) trzeba osiągnąć, zanim zaczniemy cofać gracza?
|
||||
# -1 aby wyłączyć
|
||||
max-advantage: 1
|
||||
# Po 50 sekundach z domyślną konfiguracją, przewaga gracza spadnie z 4 bloków do 1 bloku.
|
||||
# Ma to na celu zapobieżenie gromadzeniu zbyt wielu naruszeń, których gracz nie mógłby później wyzerować.
|
||||
# Domyślny sufit przewagi (oś x = sekundy, oś y = 1/1000 bloku): https://www.desmos.com/calculator/4lovswdarj
|
||||
max-ceiling: 4
|
||||
# Próg poziomu naruszeń wymagany do cofnięcia.
|
||||
# 1 dla starego zachowania.
|
||||
setback-violation-threshold: 1
|
||||
|
||||
# Sprawdza, czy gracz wszedł w blok podczas ruchu.
|
||||
Phase:
|
||||
setbackvl: 1 # Wchodzenie w bloki może umożliwiać wspinanie się po ścianach, dodatkowo ta kontrola jest względnie stabilna.
|
||||
decay: 0.005
|
||||
|
||||
AirLiquidPlace:
|
||||
cancelvl: 0
|
||||
|
||||
FabricatedPlace:
|
||||
cancelvl: 5
|
||||
|
||||
FarPlace:
|
||||
cancelvl: 5
|
||||
|
||||
PositionPlace:
|
||||
cancelvl: 5
|
||||
|
||||
RotationPlace:
|
||||
cancelvl: 5
|
||||
|
||||
# Oparta na predykcji kontrola NoSlow.
|
||||
# Grim uwzględnia tutaj problematyczny kod sieciowy... trudno o fałszywe alarmy nawet przy spamowaniu prawym przyciskiem i przyciskiem offhand.
|
||||
# Znacznie stabilniejsza niż w innych antycheatach, ale prosimy zgłaszać wszelkie fałszywe alarmy... Naprawiłem tu mnóstwo problemów z kodem sieciowym.
|
||||
NoSlow:
|
||||
# Jakie odchylenie jest uznawane za "oszukiwanie"?
|
||||
# Domyślnie jest niższe niż w innych kontrolach.
|
||||
# Konsekwentnie wykrywa odchylenia o 0.03-0.2 przy włączonym NoSlow.
|
||||
threshold: 0.001
|
||||
# Szybkie cofnięcie przy pierwszym użyciu przedmiotu, aby usunąć przewagę dawaną przez NoSlow.
|
||||
setbackvl: 5
|
||||
# Spadek naruszeń, gdy gracz używa przedmiotu I jest przez niego spowalniany.
|
||||
decay: 0.05
|
||||
|
||||
Knockback:
|
||||
# O ile powinniśmy pomnożyć całkowitą przewagę, gdy gracz gra zgodnie z zasadami?
|
||||
setback-decay-multiplier: 0.999
|
||||
# Jak duże odchylenie od prędkości nadanej graczowi (velocity) powinno skutkować naruszeniem?
|
||||
# Mierzone w blokach od możliwej prędkości.
|
||||
threshold: 0.001
|
||||
# Jak duże naruszenie w jednym ticku spowoduje natychmiastowe cofnięcie gracza?
|
||||
# -1 aby wyłączyć
|
||||
immediate-setback-threshold: 0.1
|
||||
# Jak dużą całkowitą przewagę (we wszystkich tickach) trzeba osiągnąć, zanim zaczniemy cofać gracza?
|
||||
# -1 aby wyłączyć
|
||||
max-advantage: 1
|
||||
# Ma to na celu zapobieżenie gromadzeniu zbyt wielu naruszeń, których gracz nie mógłby później wyzerować.
|
||||
max-ceiling: 4
|
||||
|
||||
Explosion:
|
||||
threshold: 0.001
|
||||
setbackvl: 3
|
||||
|
||||
TimerA:
|
||||
setbackvl: 10
|
||||
# Liczba milisekund, które gracz może zgromadzić do późniejszego wykorzystania, gdy się opóźni.
|
||||
# Może potencjalnie umożliwić obejścia fast use/fast heal/fast bow z wersji 1.8, jeśli ustawione zbyt wysoko. 120 ms wydaje się dobrym kompromisem.
|
||||
drift: 120
|
||||
|
||||
# Ta kontrola ogranicza nadużywanie balansu z checku TimerA, zapobiegając zbytniemu opóźnieniu ruchu gracza względem czasu rzeczywistego.
|
||||
TimerLimit:
|
||||
# Ping, przy którym kontrola zacznie ograniczać balans timera, aby zapobiec nadużyciom.
|
||||
# Może powodować pewne cofnięcia u uczciwych graczy, ale tylko jeśli mają ping powyżej tego progu.
|
||||
# -1 aby wyłączyć
|
||||
ping-abuse-limit-threshold: 1000
|
||||
|
||||
NegativeTimer:
|
||||
# Liczba milisekund straconych podczas ruchu, zanim zaczniemy wykrywać naruszenia.
|
||||
drift: 1200
|
||||
|
||||
# Ta sama metoda wykrywania co w TimerA, ale dla pojazdów.
|
||||
VehicleTimer:
|
||||
# Celuje w timer 1.005
|
||||
setbackvl: 10
|
||||
|
||||
PacketOrderI:
|
||||
# Włącz, jeśli gracze są oznaczani za używanie modów do animacji z 1.7.
|
||||
exempt-placing-while-digging: false
|
||||
|
||||
Reach:
|
||||
# O ile powinniśmy zwiększyć hitboxy? 0.0005 powinno wykrywać zasięg 3.0005+.
|
||||
#
|
||||
# Wymuszone zwiększenie o 0.03 występuje w wersjach 1.9-1.18.1 (z wyjątkiem 1.18.2) lub w niektórych kombinacjach klient/serwer
|
||||
# ze względu na zmiany i ograniczenia protokołu. Ta kontrola jest najskuteczniejsza dla klientów 1.7/1.8 na serwerach 1.8.
|
||||
threshold: 0.0005
|
||||
# Czy powinniśmy anulować trafienia, które wiemy, że są niemożliwe?
|
||||
# Trafienia w przedziale 3.00-3.03 mogą przejść, ale nadal będą wykrywane z powodu ograniczeń kolejności pakietów.
|
||||
block-impossible-hits: true
|
||||
# Spowoduje to wysłanie dodatkowego pakietu na końcu każdego ticka, aby zwiększyć prawdopodobieństwo wykrycia oszustw.
|
||||
# Ta metoda wstrzykuje się w listę połączeń serwera, aby wysłać końcowy pakiet tuż przed wysłaniem (flushem) danych przez serwer.
|
||||
# Włączenie tego zwiększy użycie pasma dla wszystkich graczy.
|
||||
# Nie wpłynie to negatywnie na ogólną wydajność serwera.
|
||||
# Włączenie tego pozwoli wykryć więcej oszukujących graczy.
|
||||
# Pozostawienie tego wyłączonym nadal będzie wykrywać oszustów i nie spowoduje fałszywych alarmów.
|
||||
# Jeśli nie jesteś serwerem skoncentrowanym na PvP w wersji 1.8, ten dodatkowy pakiet nie jest zalecany.
|
||||
enable-post-packet: false
|
||||
|
||||
exploit:
|
||||
# Można uzyskać duże prędkości podczas sprintu i skoku z elytrą. To ustawienie zapobiega temu exploitowi, gdy jest ustawione na false.
|
||||
# Mojang zepsuł kod sieciowy, powodując, że start lotu elytrą jest obsługiwany przez klienta, a koniec przez serwer.
|
||||
# Elytry mają poziome tarcie 0.99, więc ciągłe dodawanie poziomej prędkości 0.2 skutkuje bardzo dużymi prędkościami.
|
||||
allow-sprint-jumping-when-using-elytra: true
|
||||
# Ta opcja łagodzi efekt stawiania bloków na tzw. ghostblockach poprzez resynchronizację gracza, gdy to nastąpi.
|
||||
allow-building-on-ghostblocks: true
|
||||
# To ustawienie, zależne od powyższej wartości logicznej, określa dystans do sprawdzania ghostblocków.
|
||||
# Jego prawidłowy zakres jest ograniczony od 2 do 4.
|
||||
distance-to-check-if-ghostblocks: 2
|
||||
|
||||
# Włącz logowanie pluginów, które wstrzyknęły się do netty podczas dołączania gracza, w celu debugowania problemów z kompatybilnością.
|
||||
debug-pipeline-on-join: false
|
||||
|
||||
# Włącza eksperymentalne checki.
|
||||
experimental-checks: false
|
||||
|
||||
reset-item-usage-on-item-update: true
|
||||
reset-item-usage-on-attack: true
|
||||
reset-item-usage-on-slot-change: true
|
||||
reset-item-usage-on-item-use: true
|
||||
|
||||
# Grim czasami anuluje nielegalne pakiety, np. przy użyciu timera. Po anulowaniu X pakietów na sekundę, kiedy powinniśmy
|
||||
# po prostu wyrzucić gracza? Jest to wymagane, ponieważ niektóre limitery pakietów nie liczą pakietów anulowanych przez Grima.
|
||||
packet-spam-threshold: 100
|
||||
# Włącz to, aby wydrukować stack trace, gdy gracz zostanie wyrzucony z powodu przekroczenia packet-spam-threshold.
|
||||
debug-packet-cancel: false
|
||||
|
||||
# Grim jest w stanie egzekwować, że gracz, który nie jest w stanie lotu, nie może mieć więcej niż X milisekund pingu.
|
||||
# Wynika to z faktu, że Grim obecnie nie sprawdza latających graczy.
|
||||
# Aby wyłączyć, użyj -1.
|
||||
max-ping-out-of-flying: 1000
|
||||
|
||||
# Maksymalny ping, przy którym boost z fajerwerka jest usuwany graczowi.
|
||||
# Zapobiega to sytuacji, w której gracze z wysokim opóźnieniem mogą używać jednego boostu z fajerwerka z elytrą w nieskończoność.
|
||||
max-ping-firework-boost: 1000
|
||||
|
||||
history:
|
||||
enabled: true
|
||||
# Ile wpisów powinno być pokazywanych na każdej stronie w /grim history <player>
|
||||
entries-per-page: 15
|
||||
# Jaka powinna być wstawiona nazwa serwera dla komendy history? Jest to przydatne, jeśli używasz tej samej bazy danych dla wielu serwerów.
|
||||
server-name: Prison
|
||||
database:
|
||||
# Użyj SQLITE dla lokalnego przechowywania, użyj MYSQL lub POSTGRESQL, jeśli masz zewnętrzną bazę danych. To jest aktualizowane tylko przy restarcie serwera.
|
||||
type: SQLITE
|
||||
# Szczegóły połączenia z bazą danych
|
||||
host: localhost
|
||||
port: 3306
|
||||
database: grim
|
||||
username: root
|
||||
password: ""
|
||||
|
||||
config-version: 9
|
||||
@@ -24,6 +24,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -23,6 +23,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# 源代码链接: https://github.com/Axionize/LightningGrim
|
||||
# 版权所有 © 2025 DefineOutside 及贡献者 / 遵循 GPLv3 开源协议
|
||||
# 修改过的二进制文件,或带有复制的Grim代码的插件,必须是私有仓库,不得公开售卖此插件。
|
||||
# 中文翻译提供 Regotly / nuym / Dg32z_ , 如有错误请提交 Issues 并@我
|
||||
# 中文翻译提供 Regotly / nuym / Dg32z_ / tjshtqwq , 如有错误请提交 Issues 并@我们
|
||||
|
||||
alerts:
|
||||
# 除了向有权限的玩家发送作弊vl外,是否还在控制台中打印作弊vl消息?
|
||||
@@ -24,6 +24,7 @@ client-brand:
|
||||
ignored-clients:
|
||||
- "^vanilla$"
|
||||
- "^fabric$"
|
||||
- "^quilt$"
|
||||
- "^lunarclient:v\\d+\\.\\d+\\.\\d+-\\d{4},(?:fabric|forge)$"
|
||||
- "^Feather Fabric$"
|
||||
- "^labymod$"
|
||||
@@ -40,7 +41,7 @@ spectators:
|
||||
|
||||
# 在玩家无法接收 LightningGrim的 transaction 数据包后,多少秒对玩家进行踢出
|
||||
# 默认踢出原因为: disconnect.timeout
|
||||
# 玩家超时踢出时间(默认 = 60 秒)
|
||||
# 玩家超时踢出时间(单位:秒)(默认 = 60)
|
||||
max-transaction-time: 60
|
||||
|
||||
# 启用此选项可阻止 LightningGrim 取消玩家发送的 pong 信息。
|
||||
@@ -56,7 +57,7 @@ disable-pong-cancelling: false
|
||||
disable-default-resync-handler: false
|
||||
|
||||
# 是否对重复的移动数据包进行撤销?
|
||||
# 这可以修复 "bucket desync". https://bugs.mojang.com/browse/MC-12363,不过Mojang已经在1.21修复了这个问题
|
||||
# 这可以修复 "bucket desync"(桶不同步问题). https://bugs.mojang.com/browse/MC-12363,不过Mojang已经在1.21修复了这个问题
|
||||
# 这仅仅适用于1.8服务器中使用1.17-1.20.5版本的玩家
|
||||
cancel-duplicate-packet: true
|
||||
|
||||
@@ -64,27 +65,33 @@ cancel-duplicate-packet: true
|
||||
ignore-duplicate-packet-rotation: false
|
||||
|
||||
Simulation:
|
||||
# 当玩家合法时,我们应该将总优势乘以多少
|
||||
# 这个检查通过仿真玩家的移动,并与玩家的实际移动进行比较
|
||||
# 仿真出的位置(玩家理应到达的位置)和玩家实际位置的偏差(距离)即为移动优势。
|
||||
|
||||
# 当玩家合法时,我们应该将总移动优势乘以多少
|
||||
# 这是默认配置的样子(x 轴 = seconds ,y 轴 = 1/1000 方块): https://www.desmos.com/calculator/d4ufgxrxer
|
||||
setback-decay-multiplier: 0.999
|
||||
# 玩家移动偏移量判定阈值(单位:方块)
|
||||
# 已针对 Optifine 优化三角函数表,若设为 0.001 可减少 FastMath 误报
|
||||
# 如果此补偿不起作用,则反作弊记录此玩家作弊
|
||||
# 最大允许玩家移动优势的阈值(单位:方块)
|
||||
# 已经内置了Optifine FastMath的三角函数表,设置为0.001可避免FastMath误判
|
||||
# 若玩家移动优势大于此阈值,标记玩家作弊
|
||||
threshold: 0.001
|
||||
# 在玩家受到回弹之前,1 ticks 的判定阈值
|
||||
# 假设作弊者与模拟移动的阈值(threshold)是0.0945,该项目设置的是0.07,玩家会立即被回弹到合适的位置
|
||||
# -1 关闭
|
||||
# 若玩家的移动优势超过此值,玩家会被强制回弹
|
||||
# 假设作弊者的移动优势为0.12,且设置的是0.1,0.12 >= 0.1,玩家会立即被回弹到本应到达的位置
|
||||
# -1 关闭此项
|
||||
immediate-setback-threshold: 0.1
|
||||
# 在玩家受到回弹之前,累计回弹的阈值
|
||||
# 假设作弊者与模拟移动的阈值(threshold)是0.0945,并且触发了10次,应为0.9450
|
||||
# 而你设置的值为0.94 , 作弊者会受到回弹
|
||||
# -1 关闭
|
||||
# 若玩家的总移动优势超过此值,玩家会被强制回弹
|
||||
# 假设作弊者1个游戏刻的移动优势为0.11,并且违规了10次(10个游戏刻),即0.11*10,为1.1
|
||||
# 若设置的值为1,1.1 >= 1,那么玩家会立即被回弹到本应到达的位置
|
||||
# -1 关闭此项
|
||||
max-advantage: 1
|
||||
# 在默认配置的50秒后,玩家将从4块跑到1块
|
||||
# 这是为了防止玩家收集过多的违规行为,并且永远无法清除所有的违规行为
|
||||
# 在默认配置的50秒后,玩家的总优势将从4个方块变为1个方块
|
||||
# 这是默认配置的样子(x 轴 = seconds ,y 轴 = 1/1000 方块): https://www.desmos.com/calculator/4lovswdarj
|
||||
|
||||
# 若玩家的总移动优势大于此项,则会被重置为此项
|
||||
# 例如:总移动优势为4.2,会被强制重置到4
|
||||
# 这是为了防止玩家的总移动优势过大,并且永远无法清除总移动优势
|
||||
max-ceiling: 4
|
||||
# 设置回退的违规等级阈值
|
||||
# 设置回弹的违规等级阈值
|
||||
# 1 为旧行为
|
||||
setback-violation-threshold: 1
|
||||
|
||||
@@ -112,8 +119,8 @@ RotationPlace:
|
||||
# 在这里解释错误的服务器代码......即使在发送垃圾数据包时右键单击和副手按钮也很分辨
|
||||
# 比其他反作弊更稳定,但请报告任何错误......我在这里修复了大量的代码问题。
|
||||
NoSlow:
|
||||
# 有多少偏移量是作弊
|
||||
# 标志 0.03-0.2 与 NoSlow 一致
|
||||
# 最大允许玩家在使用物品时的移动优势(单位:方块)
|
||||
# 启用 NoSlow 后,标记量持续为 0.03-0.2
|
||||
threshold: 0.001
|
||||
# 到达多少VL时回弹
|
||||
setbackvl: 5
|
||||
@@ -123,28 +130,38 @@ NoSlow:
|
||||
Knockback:
|
||||
# 当玩家合法时,我们应该将总优势乘以多少
|
||||
setback-decay-multiplier: 0.999
|
||||
# 我们应该为玩家的移动创建多大的偏移量?
|
||||
# 从可能的移动中以方块为单位测量
|
||||
# 最大允许玩家的移动优势(单位:方块)
|
||||
threshold: 0.001
|
||||
# 在玩家受到回弹之前,1 ticks 的违规行为有多大?
|
||||
# -1 关闭
|
||||
# 若玩家的总移动优势超过此值,玩家会被强制回弹
|
||||
# 假设作弊者1个游戏刻的移动优势为0.11,并且违规了10次(10个游戏刻),即0.11*10,为1.1
|
||||
# 若设置的值为1,1.1 >= 1,那么玩家会立即被回弹到本应到达的位置
|
||||
# -1 关闭此项
|
||||
immediate-setback-threshold: 0.1
|
||||
# 在我们开始遭遇回弹之前,我们的优势有多大?
|
||||
# -1 关闭
|
||||
# 若玩家的总移动优势超过此值,玩家会被强制回弹
|
||||
# 假设作弊者1个游戏刻的移动优势为0.11,并且违规了10次(10个游戏刻),即0.11*10,为1.1
|
||||
# 若设置的值为1,1.1 >= 1,那么玩家会立即被回弹到本应到达的位置
|
||||
# -1 关闭此项
|
||||
max-advantage: 1
|
||||
# 这是为了防止玩家收集过多的违规行为,并且永远无法清除所有的违规行为
|
||||
# 若玩家的总移动优势大于此项,则会被重置为此项
|
||||
# 例如:总移动优势为4.2,会被强制重置到4
|
||||
# 这是为了防止玩家的总移动优势过大,并且永远无法清除总移动优势
|
||||
max-ceiling: 4
|
||||
|
||||
|
||||
Explosion:
|
||||
threshold: 0.001
|
||||
# 若玩家的总移动优势超过此值,玩家会被强制回弹
|
||||
# 假设作弊者1个游戏刻的移动优势为0.11,并且违规了10次(10个游戏刻),即0.11*10,为1.1
|
||||
# 若设置的值为1,1.1 >= 1,那么玩家会立即被回弹到本应到达的位置
|
||||
# -1 关闭此项
|
||||
setbackvl: 3
|
||||
# 到达多少VL时回弹
|
||||
|
||||
TimerA:
|
||||
setbackvl: 10
|
||||
# 玩家卡顿时可以累积以供以后使用的毫秒数
|
||||
# 如果设置得太高,可能会允许 1.8 快速使用/快速治疗/快速弓箭绕过,120 毫秒似乎是一个很好的平衡
|
||||
drift: 120
|
||||
# 玩家卡顿时,可以累积以供以后使用的时间(单位:毫秒)
|
||||
# 如果设置得太高,可能会允许 1.8版本的快速使用/快速回血/快速射弓绕过,120毫秒似乎是一个很好的平衡
|
||||
|
||||
# 此项是config是用来防止玩家移动与实时差距过大,来限制对 TimerA 平衡机制的滥用。
|
||||
TimerLimit:
|
||||
@@ -154,43 +171,45 @@ TimerLimit:
|
||||
ping-abuse-limit-threshold: 1000
|
||||
|
||||
NegativeTimer:
|
||||
# 在开始检查玩家时,玩家的移动丢失了多少毫秒数
|
||||
# 玩家的移动丢失了多少毫秒?
|
||||
drift: 1200
|
||||
|
||||
# 与 TimerA 相同的检查方法,但适用于坐骑
|
||||
VehicleTimer:
|
||||
# 实体1.0005 检查
|
||||
# 实体的1.0005Timer 检查
|
||||
setbackvl: 10
|
||||
# VL达到多少时回弹?
|
||||
|
||||
PacketOrderI:
|
||||
# 如果玩家因使用 1.7 动画模组而被标记,则启用此项
|
||||
# 如果玩家因使用1.7动画模组而误判,则启用此项
|
||||
exempt-placing-while-digging: false
|
||||
|
||||
Reach:
|
||||
# 我们应该将碰撞箱扩大多少? 0.0005 应该检测到 3.0005+ reach
|
||||
threshold: 0.0005
|
||||
# 我们应该将实体碰撞箱扩大多少? 0.0005 应该检测到 3.0005+ reach
|
||||
#
|
||||
# 在 1.9-1.18.1(不是 1.18.2)或某些客户端/服务器组合中存在 0.03 距离的增加,因为
|
||||
# 协议更改和限制。 对于 1.8 服务器上的 1.7/1.8 客户端,此检查功能最为强大。
|
||||
threshold: 0.0005
|
||||
# 我们应该取消我们知道不可能的命中吗?
|
||||
# 3.00-3.03 命中可能会通过,但仍会被标记,因为数据包顺序限制
|
||||
block-impossible-hits: true
|
||||
# 这将在每个ticks结束时发送一个额外的数据包,以检查作弊的可能性
|
||||
# 这会注入服务器的连接列表以在服务器刷新之前发送最终数据包
|
||||
# 启用这将增加所有玩家的带宽使用
|
||||
# 这不会降低整体服务器性能
|
||||
# 启用此功能将捕获更多作弊者。
|
||||
# 禁用此功能仍会捕获作弊者,不会导致误报
|
||||
# 除非你是专注于 1.8 PvP 的服务器,否则不建议使用这个额外的数据包
|
||||
# 我们应该取消不合法的攻击吗?
|
||||
# 攻击距离为3.00-3.03可能不会被取消,但仍会被作弊标记,因为有数据包顺序限制
|
||||
enable-post-packet: false
|
||||
# 这将在每个游戏刻(tick)结束时发送一个额外的数据包,以检查更多的作弊
|
||||
# 这会注入服务器的连接列表以在服务器刷新之前发送最终数据
|
||||
# 启用这将增加所有玩家的带宽占用
|
||||
# 但这不会降低整体服务器性能
|
||||
# 启用此功能将检查到更多作弊者。
|
||||
# 禁用此功能仍会检查到作弊者,不会导致误报
|
||||
# 除非你是专注于1.8版本的PvP服务器,否则不建议使用这个额外的数据包
|
||||
|
||||
exploit:
|
||||
#是否允许玩家在使用鞘翅时候进行的疾跑跳跃
|
||||
allow-sprint-jumping-when-using-elytra: true
|
||||
# 该选项可在发生鬼块时重新同步玩家,从而减轻玩家在鬼块上的位置。
|
||||
# 这可以在玩家遇到幽灵方块时进行同步,这可以减少玩家所碰到幽灵方块
|
||||
# 是否允许玩家在使用鞘翅时候进行的疾跑跳跃
|
||||
allow-building-on-ghostblocks: true
|
||||
# 该选项可在发生假方块时重新同步玩家方块,并允许玩家在假方块上继续放置方块
|
||||
distance-to-check-if-ghostblocks: 2
|
||||
# 检查假方块的距离。
|
||||
# 范围限制为 2 到 4
|
||||
|
||||
# 启用在加入时注入 netty 的日志插件以调试兼容性问题
|
||||
debug-pipeline-on-join: false
|
||||
@@ -204,8 +223,9 @@ reset-item-usage-on-attack: true
|
||||
reset-item-usage-on-slot-change: true
|
||||
reset-item-usage-on-item-use: true
|
||||
|
||||
# Grim有时会取消非法的数据包,比如用timer,在一秒钟内取消了数个数据包后,我们应该踢掉这个玩家?
|
||||
# 我们认为是应该的,因为有些数据包限制器并不计算被Grim取消的数据包。
|
||||
# Grim有时会取消非法的数据包,比如Timer。
|
||||
# 在一秒钟内取消了多少个数据包后,我们应该踢掉这个玩家?
|
||||
# 我们认为你应该启用此项,因为有些数据包限制器并不记录被Grim取消的数据包。
|
||||
packet-spam-threshold: 100
|
||||
# 启用此项可在玩家因 packet-spam-threshold 被踢时打印堆栈追踪
|
||||
debug-packet-cancel: false
|
||||
@@ -214,8 +234,8 @@ debug-packet-cancel: false
|
||||
# 填写-1则关闭
|
||||
max-ping-out-of-flying: 1000
|
||||
|
||||
# 限制玩家在使用鞘翅飞行时使用烟花加速的延迟
|
||||
# 这可以解决高延迟的玩家可以使用鞘翅时一直使用 1 个烟花就可以加速
|
||||
# 当玩家使用鞘翅飞行,且ping(延迟)大于多少毫秒后,Grim会取消这个玩家正在使用的烟花火箭?
|
||||
# 可以避免高延迟玩家使用一个烟花火箭就可以一直飞行
|
||||
# 填写-1则关闭
|
||||
max-ping-firework-boost: 1000
|
||||
|
||||
@@ -223,17 +243,16 @@ history:
|
||||
enabled: true
|
||||
# 使用 /grim history <player> 时每页显示多少条记录
|
||||
entries-per-page: 15
|
||||
# history 指令中插入的服务器名称?如果多个服务器共用同一数据库则很有用
|
||||
# history命令中应该插入的服务器名称是什么?如果多个服务器共用同一数据库,这个选项会很有用
|
||||
server-name: Prison
|
||||
database:
|
||||
# 本地存储使用SQLITE,如有外部数据库请使用MYSQL或POSTGRESQL。此设置仅在服务器重启时更新
|
||||
type: SQLITE
|
||||
# 数据库连接详情
|
||||
# 数据库连接信息
|
||||
host: localhost
|
||||
port: 3306
|
||||
database: grim
|
||||
username: root
|
||||
password: ""
|
||||
|
||||
# 不要调整这个
|
||||
config-version: 9
|
||||
@@ -1,13 +1,20 @@
|
||||
# Hilfe zu Webhooks: https://support.discord.com/hc/de/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
# Auf `true` setzen, um die Discord-URL-Formatprüfung zu umgehen.
|
||||
# Dies erlaubt jede gültige HTTPS-URL (nützlich für Proxys oder Matrix-Forwarder).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Warnung**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Ersetzt Backticks (`) in Werten (z. B. Client-Marken), um Fehler in der Discord-Textformatierung zu vermeiden.
|
||||
# Auf "`" setzen, um diese Ersetzung zu deaktivieren.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Spieler**: %player%"
|
||||
- "**Spieler**: `%player%`"
|
||||
- "**Überprüfung**: %check%"
|
||||
- "**Verstöße**: %violations%"
|
||||
- "**Client-Version**: %version%"
|
||||
- "**Marke**: %brand%"
|
||||
- "**Marke**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# For help with webhooks: https://support.discord.com/hc/en-us/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
# Set to true to bypass Discord's webhook URL format validation.
|
||||
# This allows any valid HTTPS URL (useful for proxies or Matrix forwarders).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Alert**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Replaces backticks in values (like client brands) so they don't break Discord's text formatting.
|
||||
# Set to "`" to disable this replacement.
|
||||
backtick-replacement-char: "ˋ" # Defaults to U+02CB
|
||||
violation-content:
|
||||
- "**Player**: %player%"
|
||||
- "**Player**: `%player%`"
|
||||
- "**Check**: %check%"
|
||||
- "**Violations**: %violations%"
|
||||
- "**Client Version**: %version%"
|
||||
- "**Brand**: %brand%"
|
||||
- "**Brand**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -7,19 +7,27 @@ enabled: false
|
||||
# La URL del webhook.
|
||||
webhook: ""
|
||||
|
||||
embed-title: "**Grim Alert**"
|
||||
# Establécelo en `true` para omitir la validación de formato de URL de Discord.
|
||||
# Esto permite cualquier URL HTTPS válida (útil para proxies o reenviadores de Matrix).
|
||||
disable-webhook-validation: false
|
||||
|
||||
embed-title: "**Grim Alerta**"
|
||||
|
||||
# El color del embed que se mandara por este webhook.
|
||||
embed-color: "#00FFFF"
|
||||
|
||||
include-timestamp: true
|
||||
|
||||
# Reemplaza las comillas invertidas (`) en los valores (como las marcas de clientes) para no romper el formato de texto de Discord.
|
||||
# Establécelo en "`" para desactivar este reemplazo.
|
||||
backtick-replacement-char: "ˋ"
|
||||
|
||||
# El contenido del embed que se mandara por este webhook.
|
||||
violation-content:
|
||||
- "**Jugador**: %player%"
|
||||
- "**Jugador**: `%player%`"
|
||||
- "**Comprobación**: %check%"
|
||||
- "**Violaciones**: %violations%"
|
||||
- "**Version del cliente**: %version%"
|
||||
- "**Marca del cliente**: %brand%"
|
||||
- "**Marca del cliente**: `%brand%`"
|
||||
- "**Latencia**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# Aide sur les webhooks : https://support.discord.com/hc/fr/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Définissez sur `true` pour contourner la validation du format d'URL de Discord.
|
||||
# Cela autorise n'importe quelle URL HTTPS valide (utile pour les proxys ou redirecteurs Matrix).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Alerte**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Remplace les accents graves (`) dans les valeurs (ex. marques de clients) pour éviter de casser le formatage du texte sur Discord.
|
||||
# Définissez sur "`" pour désactiver ce remplacement.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Joueur**: %player%"
|
||||
- "**Joueur**: `%player%`"
|
||||
- "**Vérification**: %check%"
|
||||
- "**Violations**: %violations%"
|
||||
- "**Version du client**: %version%"
|
||||
- "**Nature du client**: %brand%"
|
||||
- "**Marque du client**: `%brand%`"
|
||||
- "**Latence**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# Guida sui webhook: https://support.discord.com/hc/it/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Imposta su `true` per ignorare la validazione del formato URL di Discord.
|
||||
# Questo consente qualsiasi URL HTTPS valida (utile per proxy o forwarder Matrix).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Avviso**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Sostituisce gli apici inversi (`) nei valori (come i brand dei client) per evitare di rompere la formattazione del testo di Discord.
|
||||
# Imposta su "`" per disabilitare questa sostituzione.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Giocatore**: %player%"
|
||||
- "**Cheats Rilevati**: %check%"
|
||||
- "**Giocatore**: `%player%`"
|
||||
- "**Controllo**: %check%"
|
||||
- "**Violazioni**: %violations%"
|
||||
- "**Versione Client**: %version%"
|
||||
- "**Client**: %brand%"
|
||||
- "**Versione del client**: %version%"
|
||||
- "**Marca del client**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -3,14 +3,20 @@
|
||||
# https://support.discord.com/hc/ja/articles/228383668-Webhooks-%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim アラート**"
|
||||
# DiscordのURLフォーマット検証をバイパスするには `true` に設定します。
|
||||
# これにより、任意の有効なHTTPS URLが許可されます(プロキシやMatrixフォワーダーに便利です)。
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim 警告**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# 値(クライアントのブランド名など)に含まれるバッククォート (`) を置換し、Discordのテキスト書式が崩れるのを防ぎます。
|
||||
# この置換を無効にするには "`" を設定してください。
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**プレイヤー**: %player%"
|
||||
- "**プレイヤー**: `%player%`"
|
||||
- "**違反項目**: %check%"
|
||||
- "**違反回数**: %violations%"
|
||||
- "**クライアントバージョン**: %version%"
|
||||
- "**ブランド**: %brand%"
|
||||
- "**ブランド**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# Hulp bij webhooks: https://support.discord.com/hc/nl/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Stel in op `true` om de URL-formaatcontrole van Discord te omzeilen.
|
||||
# Dit staat elke geldige HTTPS-URL toe (handig voor proxy's of Matrix-forwarders).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Waarschuwing**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Vervangt backticks (`) in waarden (zoals clientmerken) om te voorkomen dat de tekstopmaak van Discord breekt.
|
||||
# Stel in op "`" om deze vervanging uit te schakelen.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Speler**: %player%"
|
||||
- "**Controleer**: %check%"
|
||||
- "**Speler**: `%player%`"
|
||||
- "**Controle**: %check%"
|
||||
- "**Overtredingen**: %violations%"
|
||||
- "**Client-versie**: %version%"
|
||||
- "**Client-merk**: %brand%"
|
||||
- "**Client-merk**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# Ajuda com webhooks: https://support.discord.com/hc/pt-br/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Defina como `true` para ignorar a validação de formato de URL do Discord.
|
||||
# Isso permite qualquer URL HTTPS válida (útil para proxies ou encaminhadores do Matrix).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Alerta**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Substitui as aspas invertidas (`) nos valores (como marcas de clientes) para evitar a quebra de formatação de texto do Discord.
|
||||
# Defina como "`" para desativar esta substituição.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Jogador**: %player%"
|
||||
- "**Jogador**: `%player%`"
|
||||
- "**Verificação**: %check%"
|
||||
- "**Violações**: %violations%"
|
||||
- "**Versão do Cliente**: %version%"
|
||||
- "**Nome do Cliente**: %brand%"
|
||||
- "**Marca do Cliente**: `%brand%`"
|
||||
- "**Ping**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,14 +1,20 @@
|
||||
# Включать ли веб-крючок discord
|
||||
# Справка по вебхукам: https://support.discord.com/hc/ru/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Установите значение `true`, чтобы обойти проверку формата URL Discord.
|
||||
# Это разрешает любой действительный HTTPS URL (полезно для прокси или Matrix forwarder).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Оповещение**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Заменяет обратные кавычки (`) в значениях (например, в названиях клиентов), чтобы не нарушать форматирование текста в Discord.
|
||||
# Установите значение "`", чтобы отключить эту замену.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Игрок**: %player%"
|
||||
- "**Игрок**: `%player%`"
|
||||
- "**Проверка**: %check%"
|
||||
- "**Нарушения**: %violations%"
|
||||
- "**Версия Клиента**: %version%"
|
||||
- "**Бренд**: %brand%"
|
||||
- "**Бренд**: `%brand%`"
|
||||
- "**Пинг**: %ping%"
|
||||
- "**ТПС**: %tps%"
|
||||
@@ -1,13 +1,20 @@
|
||||
# Webhook yardımı: https://support.discord.com/hc/tr/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# Discord'un URL biçimi doğrulamasını atlamak için `true` olarak ayarlayın.
|
||||
# Bu, geçerli herhangi bir HTTPS URL'sine izin verir (proxy'ler veya Matrix yönlendiricileri için kullanışlıdır).
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim Uyarı**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# Discord'un metin biçimlendirmesini bozmasını önlemek için değerlerdeki (istemci markaları gibi) ters tırnak işaretlerini (`) değiştirir.
|
||||
# Bu değiştirmeyi devre dışı bırakmak için "`" olarak ayarlayın.
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**Oyuncu**: %player%"
|
||||
- "**Oyuncu**: `%player%`"
|
||||
- "**Kontrol**: %check%"
|
||||
- "**İhlaller**: %violations%"
|
||||
- "**Client Versiyonu**: %version%"
|
||||
- "**Kullanılan Client**: %brand%"
|
||||
- "**Client Markası**: `%brand%`"
|
||||
- "**Gecikme**: %ping%"
|
||||
- "**TPS**: %tps%"
|
||||
@@ -1,14 +1,20 @@
|
||||
# 是否启用discord webhook
|
||||
# Webhook帮助: https://support.discord.com/hc/zh-cn/articles/228383668
|
||||
enabled: false
|
||||
webhook: ""
|
||||
embed-title: "**Grim Alert**"
|
||||
# 设置为 `true` 以绕过 Discord 的 URL 格式验证。
|
||||
# 这将允许任何有效的 HTTPS URL(适用于代理或 Matrix 转发器)。
|
||||
disable-webhook-validation: false
|
||||
embed-title: "**Grim 警报**"
|
||||
embed-color: "#00FFFF"
|
||||
include-timestamp: true
|
||||
# 替换变量值(如客户端品牌)中的反引号 (`),以防止破坏 Discord 的文本格式。
|
||||
# 将其设置为 "`" 即可禁用此替换。
|
||||
backtick-replacement-char: "ˋ"
|
||||
violation-content:
|
||||
- "**玩家名**: %player%"
|
||||
- "**玩家名**: `%player%`"
|
||||
- "**检查**: %check%"
|
||||
- "**违规率**: %violations%"
|
||||
- "**违规次数**: %violations%"
|
||||
- "**游戏版本**: %version%"
|
||||
- "**客户端品牌**: %brand%"
|
||||
- "**客户端品牌**: `%brand%`"
|
||||
- "**延迟**: %ping%"
|
||||
- "**服务器卡顿程度**: %tps%"
|
||||
- "**TPS**: %tps%"
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user