Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49742a9ddc | ||
|
|
612e0f8f67 | ||
|
|
5d4af9365b | ||
|
|
5489f118c1 | ||
|
|
ded5c0f320 | ||
|
|
238b77c002 | ||
|
|
71d93c97cf | ||
|
|
43f4c7fcd7 | ||
|
|
03714c9b97 | ||
|
|
004804c09e | ||
|
|
853d02b9c8 | ||
|
|
2272d4df16 | ||
|
|
fa2dc7b00b | ||
|
|
c8347ed146 | ||
|
|
c94260bea6 | ||
|
|
bde2328511 | ||
|
|
b87eeaf490 | ||
|
|
3abc904590 | ||
|
|
1b416fc841 | ||
|
|
224f464eff | ||
|
|
ac35735fff | ||
|
|
def21633e2 | ||
|
|
918e6575e8 | ||
|
|
ea1f1615b6 | ||
|
|
c323cf06ce | ||
|
|
0ba331780e | ||
|
|
fc47c5b663 | ||
|
|
17cf067d58 | ||
|
|
6280b28e16 | ||
|
|
bfd2bd0e22 | ||
|
|
0df77dd32a | ||
|
|
b373e5f1c4 | ||
|
|
115c32916f | ||
|
|
5f8692deda | ||
|
|
f8a5315fff | ||
|
|
102b5842e7 | ||
|
|
a76bf9ce09 | ||
|
|
4f3e715f94 | ||
|
|
1b47e31776 | ||
|
|
b69906f809 | ||
|
|
9ba4052c35 | ||
|
|
a86bf57a39 | ||
|
|
a82f2b888a | ||
|
|
89cf181772 | ||
|
|
6e5ec332dd | ||
|
|
79bf7e4fd1 | ||
|
|
d0aafdd9f7 | ||
|
|
3f19abc859 | ||
|
|
8a6863b9d3 | ||
|
|
26492fd8e3 | ||
|
|
15e809835b | ||
|
|
1eae5484c7 | ||
|
|
90036e2bf2 |
No files matched your search
@@ -38,7 +38,8 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
# Grant permission to write contents (for creating tags/refs)
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
@@ -85,7 +86,7 @@ jobs:
|
||||
run: |
|
||||
# If any command in a pipeline fails, treat the pipeline as failed.
|
||||
set -o pipefail
|
||||
|
||||
|
||||
BUILD_ARGS="build"
|
||||
if [[ "${{ inputs.is-release-build }}" == "true" ]]; then
|
||||
BUILD_ARGS="$BUILD_ARGS -Prelease=true"
|
||||
@@ -94,12 +95,12 @@ jobs:
|
||||
if [[ "${{ inputs.run-build-scan }}" == "true" ]]; then
|
||||
BUILD_ARGS="$BUILD_ARGS --scan"
|
||||
fi
|
||||
|
||||
|
||||
echo "Running: ./gradlew printVersion $BUILD_ARGS"
|
||||
|
||||
|
||||
# Use 'tee' to both display the output live and save it to a file for parsing
|
||||
VERSION=$(./gradlew printVersion $BUILD_ARGS | tee build-output.log | grep '^VERSION=' | tail -n1 | cut -d'=' -f2)
|
||||
|
||||
|
||||
echo "build_version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Build version: $VERSION"
|
||||
@@ -110,7 +111,7 @@ jobs:
|
||||
run: |
|
||||
# Search the log file for the build scan URL and extract it
|
||||
SCAN_URL=$(grep -o 'https://gradle.com/s/[^ ]*' build-output.log | head -n 1 || true)
|
||||
|
||||
|
||||
if [[ -n "$SCAN_URL" ]]; then
|
||||
echo "Found build scan URL: $SCAN_URL"
|
||||
# Post a highly visible link to the Job Summary page
|
||||
@@ -165,17 +166,25 @@ jobs:
|
||||
# Use the environment variable for the dev changelog.
|
||||
NOTES="$DEV_NOTES"
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$NOTES" ]; then
|
||||
NOTES="* No new code changes in this build."
|
||||
fi
|
||||
|
||||
|
||||
# This is the correct way to set a multi-line output variable in Actions
|
||||
{
|
||||
echo "notes<<EOF"
|
||||
echo "$NOTES"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate build number
|
||||
if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) && inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != ''
|
||||
id: buildnumber
|
||||
uses: onyxmueller/build-tag-number@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# Publish to Modrinth & API
|
||||
#------------------------------------------------------------------
|
||||
@@ -188,17 +197,17 @@ jobs:
|
||||
modrinth-featured: ${{ inputs.is-release-build }} # Only feature release builds
|
||||
|
||||
files: bukkit/build/libs/grimac-bukkit-${{ env.BUILD_VERSION }}.jar
|
||||
name: Grim Anticheat (Bukkit) ${{ env.BUILD_VERSION }}
|
||||
name: LightningGrim Anticheat (Bukkit) ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
|
||||
version: ${{ env.BUILD_VERSION }}
|
||||
version-type: ${{ inputs.release-version-type }}
|
||||
changelog: ${{ steps.changelog.outputs.notes }}
|
||||
loaders: |
|
||||
bukkit
|
||||
spigot
|
||||
paper
|
||||
folia
|
||||
purpur
|
||||
game-versions: '>=1.7 <=1.21.8'
|
||||
bukkit
|
||||
spigot
|
||||
paper
|
||||
folia
|
||||
purpur
|
||||
game-versions: '>=1.7 <=1.21.10'
|
||||
|
||||
- name: Publish Fabric to Modrinth
|
||||
if: ${{ inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != '' }}
|
||||
@@ -209,13 +218,13 @@ jobs:
|
||||
modrinth-featured: ${{ inputs.is-release-build }} # Only feature release builds
|
||||
|
||||
files: fabric/build/libs/grimac-fabric-${{ env.BUILD_VERSION }}.jar
|
||||
name: Grim Anticheat (Fabric) ${{ env.BUILD_VERSION }}
|
||||
name: LightningGrim Anticheat (Fabric) ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
|
||||
version: ${{ env.BUILD_VERSION }}
|
||||
version-type: ${{ inputs.release-version-type }}
|
||||
changelog: ${{ steps.changelog.outputs.notes }}
|
||||
loaders: |
|
||||
fabric
|
||||
game-versions: '>=1.16.1 <=1.21.8'
|
||||
game-versions: '>=1.16.1 <=1.21.10'
|
||||
dependencies: 'fabric-api'
|
||||
|
||||
- name: Publish to API
|
||||
|
||||
@@ -12,64 +12,64 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'java-kotlin' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'java-kotlin' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
settings-path: ${{ github.workspace }} # location for the settings.xml file
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -19,6 +19,7 @@ on:
|
||||
- 'dev'
|
||||
- 'release'
|
||||
- 'release/**'
|
||||
- 'lightning'
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'buildSrc/**'
|
||||
|
||||
@@ -65,8 +65,8 @@ information.
|
||||
|
||||
## Compiling From Source
|
||||
|
||||
1. `git clone https://github.com/GrimAnticheat/Grim.git`
|
||||
2. `cd Grim`
|
||||
1. `git clone https://github.com/Axionize/LightningGrim`
|
||||
2. `cd LightningGrim`
|
||||
3. `./gradlew build`
|
||||
4. The final jars will compile into the `<platform>/build/libs` folders
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ object VersionUtil {
|
||||
val mainBranch = System.getenv("GRIM_MAIN_BRANCH") ?: "2.0"
|
||||
|
||||
return when (branch) {
|
||||
"lightning" -> null
|
||||
"main", mainBranch -> null // ← ignore these branches
|
||||
else -> branch
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ publishing.publications.create<MavenPublication>("maven") {
|
||||
|
||||
tasks {
|
||||
runServer {
|
||||
minecraftVersion("1.21.4")
|
||||
minecraftVersion("1.21.10")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
@@ -6,6 +6,8 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
|
||||
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
|
||||
import ac.grim.grimac.platform.bukkit.world.BukkitPlatformWorld;
|
||||
import ac.grim.grimac.utils.math.Location;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -15,6 +17,8 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitGrimEntity implements GrimEntity {
|
||||
|
||||
protected static final boolean CAN_USE_DIRECT_GETTERS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_20_1) && PaperUtils.PAPER;
|
||||
|
||||
private final Entity entity;
|
||||
private BukkitPlatformWorld bukkitPlatformWorld;
|
||||
|
||||
@@ -59,7 +63,6 @@ public class BukkitGrimEntity implements GrimEntity {
|
||||
if (bukkitPlatformWorld == null || !bukkitPlatformWorld.getBukkitWorld().equals(entity.getWorld())) {
|
||||
bukkitPlatformWorld = new BukkitPlatformWorld(entity.getWorld());
|
||||
}
|
||||
|
||||
return bukkitPlatformWorld;
|
||||
}
|
||||
|
||||
@@ -75,4 +78,26 @@ public class BukkitGrimEntity implements GrimEntity {
|
||||
location.getPitch()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double distanceSquared(double oX, double oY, double oZ) {
|
||||
if (CAN_USE_DIRECT_GETTERS) {
|
||||
double x = this.entity.getX();
|
||||
double y = this.entity.getY();
|
||||
double z = this.entity.getZ();
|
||||
double distX = (x - oX) * (x - oX);
|
||||
double distY = (y - oY) * (y - oY);
|
||||
double distZ = (z - oZ) * (z - oZ);
|
||||
return distX + distY + distZ;
|
||||
} else {
|
||||
org.bukkit.Location location = this.entity.getLocation();
|
||||
double x = location.getX();
|
||||
double y = location.getY();
|
||||
double z = location.getZ();
|
||||
double distX = (x - oX) * (x - oX);
|
||||
double distY = (y - oY) * (y - oY);
|
||||
double distZ = (z - oZ) * (z - oZ);
|
||||
return distX + distY + distZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,8 @@ import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.PistonData;
|
||||
import ac.grim.grimac.utils.data.PistonTemplate;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -28,10 +26,10 @@ public class PistonEvent implements Listener {
|
||||
private static final double MAX_VERTICAL_DISTANCE = 64.0;
|
||||
|
||||
// accuracy isn't that important, it's close enough and performant
|
||||
private static boolean isCloseEnough(Vector3i vectorA, Vector3d vectorB) {
|
||||
return Math.abs(vectorA.getX() - vectorB.getX()) <= MAX_HORIZONTAL_DISTANCE
|
||||
&& Math.abs(vectorA.getY() - vectorB.getY()) <= MAX_VERTICAL_DISTANCE
|
||||
&& Math.abs(vectorA.getZ() - vectorB.getZ()) <= MAX_HORIZONTAL_DISTANCE;
|
||||
private static boolean isCloseEnough(int x, int y, int z, double posX, double posY, double posZ) {
|
||||
return Math.abs(x - posX) <= MAX_HORIZONTAL_DISTANCE
|
||||
&& Math.abs(y - posY) <= MAX_VERTICAL_DISTANCE
|
||||
&& Math.abs(z - posZ) <= MAX_HORIZONTAL_DISTANCE;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@@ -68,18 +66,8 @@ public class PistonEvent implements Listener {
|
||||
piston.getY() + event.getDirection().getModY(),
|
||||
piston.getZ() + event.getDirection().getModZ()));
|
||||
|
||||
final int chunkX = event.getBlock().getX() >> 4;
|
||||
final int chunkZ = event.getBlock().getZ() >> 4;
|
||||
final BlockFace blockFace = BukkitConversionUtils.fromBukkitFace(event.getDirection());
|
||||
final Vector3i sourcePos = new Vector3i(piston.getX(), piston.getY(), piston.getZ());
|
||||
|
||||
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
|
||||
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
|
||||
final int lastTrans = player.lastTransactionSent.get();
|
||||
PistonData data = new PistonData(blockFace, boxes, lastTrans, true, hasSlimeBlock, hasHoneyBlock);
|
||||
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
|
||||
}
|
||||
}
|
||||
PistonTemplate data = new PistonTemplate(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, true, hasSlimeBlock, hasHoneyBlock);
|
||||
addPistonData(data, piston.getX(), piston.getY(), piston.getZ());
|
||||
}
|
||||
|
||||
// For some unknown reason, bukkit handles this stupidly
|
||||
@@ -101,9 +89,8 @@ public class PistonEvent implements Listener {
|
||||
BlockFace face = BukkitConversionUtils.fromBukkitFace(event.getDirection());
|
||||
|
||||
// The event was called without blocks and is therefore in the right direction
|
||||
Block piston = event.getBlock();
|
||||
if (event.getBlocks().isEmpty()) {
|
||||
Block piston = event.getBlock();
|
||||
|
||||
// Add bounding box of the actual piston head pushing
|
||||
boxes.add(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true)
|
||||
.offset(piston.getX() + face.getModX(),
|
||||
@@ -127,16 +114,19 @@ public class PistonEvent implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
final int chunkX = event.getBlock().getX() >> 4;
|
||||
final int chunkZ = event.getBlock().getZ() >> 4;
|
||||
Vector3i sourcePos = new Vector3i(event.getBlock().getX(), event.getBlock().getY(), event.getBlock().getZ());
|
||||
PistonTemplate data = new PistonTemplate(face, boxes, false, hasSlimeBlock, hasHoneyBlock);
|
||||
addPistonData(data, piston.getX(), piston.getY(), piston.getZ());
|
||||
}
|
||||
|
||||
private void addPistonData(PistonTemplate pistonTemplate, int blockX, int blockY, int blockZ) {
|
||||
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
|
||||
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
|
||||
int lastTrans = player.lastTransactionSent.get();
|
||||
PistonData data = new PistonData(face, boxes, lastTrans, false, hasSlimeBlock, hasHoneyBlock);
|
||||
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
|
||||
}
|
||||
if (player.compensatedWorld.isChunkLoaded(blockX >> 4, blockZ >> 4) || !isCloseEnough(blockX, blockY, blockZ, player.x, player.y, player.z)) continue;
|
||||
|
||||
int lastTrans = player.lastTransactionSent.get();
|
||||
|
||||
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> {
|
||||
player.compensatedWorld.addPiston(pistonTemplate, lastTrans);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -177,10 +177,11 @@ public class BukkitItemResetHandler implements ItemResetHandler {
|
||||
if (legacy) { // 1.8.8
|
||||
resetItemUsage = player -> {
|
||||
try {
|
||||
method.invoke(getHandle.invoke(player));
|
||||
|
||||
// in 1.8 we need to resync item usage manually,
|
||||
// only do so if the player is using an item
|
||||
if (isUsingItem.test(player)) player.updateInventory();
|
||||
method.invoke(getHandle.invoke(player));
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
+28
-4
@@ -9,8 +9,11 @@ import ac.grim.grimac.platform.bukkit.entity.BukkitGrimEntity;
|
||||
import ac.grim.grimac.platform.bukkit.utils.anticheat.MultiLibUtil;
|
||||
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
|
||||
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import ac.grim.grimac.utils.math.Location;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
|
||||
import lombok.Getter;
|
||||
@@ -27,6 +30,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPlayer {
|
||||
|
||||
private static final BukkitAudiences audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
|
||||
|
||||
@Getter
|
||||
@@ -34,10 +38,18 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
@Getter
|
||||
private final PlatformInventory inventory;
|
||||
|
||||
private final @Nullable User user;
|
||||
|
||||
public BukkitPlatformPlayer(Player bukkitPlayer) {
|
||||
super(bukkitPlayer);
|
||||
this.bukkitPlayer = bukkitPlayer;
|
||||
this.inventory = new BukkitPlatformInventory(bukkitPlayer);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
|
||||
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(bukkitPlayer.getUniqueId());
|
||||
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
|
||||
} else {
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,12 +79,20 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
bukkitPlayer.sendMessage(message);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
bukkitPlayer.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component message) {
|
||||
audiences.player(bukkitPlayer).sendMessage(message);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
audiences.player(bukkitPlayer).sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,8 +112,12 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
|
||||
@Override
|
||||
public Vector3d getPosition() {
|
||||
org.bukkit.Location location = this.bukkitPlayer.getLocation();
|
||||
return new Vector3d(location.getX(), location.getY(), location.getZ());
|
||||
if (CAN_USE_DIRECT_GETTERS) {
|
||||
return new Vector3d(this.bukkitPlayer.getX(), this.bukkitPlayer.getY(), this.bukkitPlayer.getZ());
|
||||
} else {
|
||||
org.bukkit.Location location = this.bukkitPlayer.getLocation();
|
||||
return new Vector3d(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,7 +31,6 @@ import lombok.Getter;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@Getter
|
||||
public final class GrimAPI {
|
||||
public static final GrimAPI INSTANCE = new GrimAPI();
|
||||
|
||||
@@ -170,7 +170,8 @@ public class Check extends GrimProcessor implements AbstractCheck {
|
||||
}
|
||||
|
||||
public static boolean isAsync(PacketTypeCommon packetType) {
|
||||
return packetType == PacketType.Play.Client.KEEP_ALIVE;
|
||||
return packetType == PacketType.Play.Client.KEEP_ALIVE
|
||||
|| packetType == PacketType.Play.Client.CHUNK_BATCH_ACK;
|
||||
}
|
||||
|
||||
public boolean isUpdate(PacketTypeCommon packetType) {
|
||||
|
||||
@@ -8,7 +8,7 @@ public abstract class AbstractDebugHandler extends Check {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public abstract void toggleListener(GrimPlayer player);
|
||||
public abstract boolean toggleListener(GrimPlayer player);
|
||||
|
||||
public abstract boolean toggleConsoleOutput();
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package ac.grim.grimac.checks.debug;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Handles debug visualization of hitboxes and reach calculations for GrimAC.
|
||||
* Sends debug data to clients through plugin messages that can be visualized by compatible clients.
|
||||
*/
|
||||
public class HitboxDebugHandler extends AbstractDebugHandler {
|
||||
|
||||
/**
|
||||
* Set of players currently listening to hitbox debug data
|
||||
*/
|
||||
private final Set<GrimPlayer> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Creates a new HitboxDebugHandler for the specified player
|
||||
*
|
||||
* @param grimPlayer The GrimAC player instance to debug
|
||||
*/
|
||||
public HitboxDebugHandler(GrimPlayer grimPlayer) {
|
||||
super(grimPlayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether a player receives hitbox debug visualization data.
|
||||
* If the player is already listening, they will be removed. If not, they will be added.
|
||||
*
|
||||
* @param player The player to toggle debug visualization for
|
||||
* @return {@code true} if the player is now listening (was added),
|
||||
* {@code false} if the player is no longer listening (was removed).
|
||||
*/
|
||||
@Override
|
||||
public boolean toggleListener(GrimPlayer player) {
|
||||
boolean wasPresent = listeners.remove(player);
|
||||
|
||||
if (wasPresent) {
|
||||
return false;
|
||||
} else {
|
||||
listeners.add(player);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean toggleConsoleOutput() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends debug visualization data for reach calculations to all listening players.
|
||||
* The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations.
|
||||
*
|
||||
* @param hitboxes Map of entity IDs to their collision boxes
|
||||
* @param targetEntities Set of entity IDs that are considered targets
|
||||
* @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights
|
||||
* @param basePos Base position before eye height adjustments
|
||||
* @param isPrediction Whether these hitboxes are from a prediction calculation
|
||||
*
|
||||
* Packet Format (Version 1):
|
||||
* - Byte: Version (0)
|
||||
* - Byte: Global flags
|
||||
* - Bit 0: isPrediction
|
||||
* - Bits 1-7: Reserved
|
||||
* - Double: Player reach/interact distance
|
||||
* - Vector3d: Base position (3 doubles)
|
||||
* - VarInt: Number of ray traces
|
||||
* - For each ray trace:
|
||||
* - Double: Eye height delta
|
||||
* - Vector3d: Look vector (3 doubles)
|
||||
* - VarInt: Number of hitboxes
|
||||
* - For each hitbox:
|
||||
* - VarInt: Entity ID
|
||||
* - Byte: Flags
|
||||
* - Bit 0: Is target entity
|
||||
* - Bit 1: Is no collision
|
||||
* - Bits 2-7: Reserved
|
||||
* - If not NoCollisionBox:
|
||||
* - Double: minX
|
||||
* - Double: minY
|
||||
* - Double: minZ
|
||||
* - Double: maxX
|
||||
* - Double: maxY
|
||||
* - Double: maxZ
|
||||
*/
|
||||
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
|
||||
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, Vector3dm basePos,
|
||||
boolean isPrediction, double reachDistance) {
|
||||
if (!isEnabled()) return;
|
||||
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
try {
|
||||
// Version Header
|
||||
buffer.writeByte(1);
|
||||
|
||||
// Global Flags Header
|
||||
// Pack boolean flags into a single byte
|
||||
byte global_flags = 0;
|
||||
global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction?
|
||||
// Bits 2-7 reserved for future use
|
||||
buffer.writeByte(global_flags);
|
||||
|
||||
// Write reach distance
|
||||
buffer.writeDouble(reachDistance);
|
||||
|
||||
// Write base position
|
||||
writeVector(buffer, basePos);
|
||||
|
||||
// Write number of ray traces
|
||||
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
|
||||
|
||||
// Write all possible ray traces
|
||||
for (Pair<Vector3dm, Double> pair : lookVecsAndEyeHeights) {
|
||||
Vector3dm lookVec = pair.first();
|
||||
double eyeHeight = pair.second();
|
||||
|
||||
// Write eye height delta and look vector
|
||||
// we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer
|
||||
// then they need to be so grim can report correct reach distance for too far away hits
|
||||
buffer.writeDouble(eyeHeight);
|
||||
writeVector(buffer, lookVec);
|
||||
}
|
||||
|
||||
// Write number of hitboxes
|
||||
ByteBufHelper.writeVarInt(buffer, hitboxes.size());
|
||||
|
||||
// Write hitbox data
|
||||
for (Map.Entry<Integer, CollisionBox> entry : hitboxes.entrySet()) {
|
||||
int entityId = entry.getKey();
|
||||
CollisionBox box = entry.getValue();
|
||||
|
||||
// Write entity ID
|
||||
ByteBufHelper.writeVarInt(buffer, entityId);
|
||||
|
||||
// Pack boolean flags into a single byte
|
||||
byte flags = 0;
|
||||
flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target
|
||||
flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision
|
||||
// Bits 2-7 reserved for future use
|
||||
buffer.writeByte(flags);
|
||||
|
||||
// Write box coordinates if it's not a NoCollisionBox
|
||||
if ((flags & 2) == 0) {
|
||||
SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box;
|
||||
buffer.writeDouble(simpleCollisionBox.minX);
|
||||
buffer.writeDouble(simpleCollisionBox.minY);
|
||||
buffer.writeDouble(simpleCollisionBox.minZ);
|
||||
buffer.writeDouble(simpleCollisionBox.maxX);
|
||||
buffer.writeDouble(simpleCollisionBox.maxY);
|
||||
buffer.writeDouble(simpleCollisionBox.maxZ);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert buffer to byte array
|
||||
byte[] data = new byte[buffer.readableBytes()];
|
||||
buffer.readBytes(data);
|
||||
|
||||
// Create and send packet
|
||||
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(
|
||||
"grim:debug_hitbox",
|
||||
data
|
||||
);
|
||||
|
||||
// Send to all listeners
|
||||
for (GrimPlayer listener : listeners) {
|
||||
if (listener != null) {
|
||||
listener.user.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Release buffer to prevent memory leaks
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return !listeners.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to write a Vector to the ByteBuf
|
||||
* @param buffer The buffer to write to
|
||||
* @param vector The vector to write
|
||||
*/
|
||||
private void writeVector(ByteBuf buffer, Vector3dm vector) {
|
||||
buffer.writeDouble(vector.getX());
|
||||
buffer.writeDouble(vector.getY());
|
||||
buffer.writeDouble(vector.getZ());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package ac.grim.grimac.checks.impl.badpackets;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@CheckData(name = "BadPacketsZ", experimental = true)
|
||||
public class BadPacketsZ extends Check implements PacketCheck {
|
||||
private boolean sent;
|
||||
|
||||
public BadPacketsZ(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
|
||||
sent = false;
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLAYER_INPUT) {
|
||||
if (sent) {
|
||||
flagAndAlert();
|
||||
}
|
||||
|
||||
sent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,8 @@ public class FarBreak extends Check implements BlockBreakCheck {
|
||||
double min = Double.MAX_VALUE;
|
||||
for (double d : player.getPossibleEyeHeights()) {
|
||||
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
|
||||
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
|
||||
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
|
||||
min = Math.min(min, eyes.distanceSquared(best));
|
||||
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
|
||||
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
|
||||
}
|
||||
|
||||
// getPickRange() determines this?
|
||||
|
||||
@@ -7,7 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
|
||||
@CheckData(name = "PositionBreakB", experimental = true)
|
||||
@CheckData(name = "PositionBreakB")
|
||||
public class PositionBreakB extends Check implements BlockBreakCheck {
|
||||
private final int releaseFace = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) ? 0 : 255;
|
||||
private BlockFace lastFace;
|
||||
|
||||
@@ -4,8 +4,6 @@ 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 ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.via.ViaHooks;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
@@ -14,7 +12,6 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCh
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatCommandUnsigned;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatMessage;
|
||||
|
||||
|
||||
// this can false from click events, but I doubt this would actually
|
||||
// happen unless they're trying to flag, or if the server is set up badly
|
||||
@CheckData(name = "ChatB", description = "Invalid chat message")
|
||||
@@ -64,10 +61,4 @@ public class ChatB extends Check implements PacketCheck {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static {
|
||||
if (ViaVersionUtil.isAvailable && PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_11)) {
|
||||
ViaHooks.injectVia();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ac.grim.grimac.checks.impl.combat;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "EntityPierce", configName = "EntityPierce", setback = 30)
|
||||
public class EntityPierce extends Check implements PacketCheck {
|
||||
public EntityPierce(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,22 @@ package ac.grim.grimac.checks.impl.combat;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.EntityHitData;
|
||||
import ac.grim.grimac.utils.data.HitData;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntitySizeable;
|
||||
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtils;
|
||||
import ac.grim.grimac.utils.nmsutil.WorldRayTrace;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
|
||||
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
|
||||
@@ -32,16 +41,17 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
// You may not copy the check unless you are licensed under GPL
|
||||
@CheckData(name = "Reach", setback = 10)
|
||||
@@ -55,8 +65,14 @@ public class Reach extends Check implements PacketCheck {
|
||||
// Only one flag per reach attack, per entity, per tick.
|
||||
// We store position because lastX isn't reliable on teleports.
|
||||
private final Int2ObjectMap<Vector3d> playerAttackQueue = new Int2ObjectOpenHashMap<>();
|
||||
// temporarily used to prevent falses in the wall hit check
|
||||
private final Set<Vector3i> blocksChangedThisTick = new HashSet<>();
|
||||
// extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit
|
||||
public static final double extraSearchDistance = 3;
|
||||
|
||||
private boolean ignoreNonPlayerTargets;
|
||||
private boolean cancelImpossibleHits;
|
||||
private double threshold;
|
||||
public double threshold;
|
||||
private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach
|
||||
|
||||
public Reach(GrimPlayer player) {
|
||||
@@ -88,6 +104,10 @@ public class Reach extends Check implements PacketCheck {
|
||||
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;
|
||||
|
||||
@@ -114,8 +134,9 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
|
||||
// If the player set their look, or we know they have a new tick
|
||||
final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType());
|
||||
if (isUpdate(event.getPacketType())) {
|
||||
tickBetterReachCheckWithAngle();
|
||||
tickBetterReachCheckWithAngle(isFlying);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +170,7 @@ public class Reach extends Check implements PacketCheck {
|
||||
}
|
||||
}
|
||||
|
||||
private void tickBetterReachCheckWithAngle() {
|
||||
private void tickBetterReachCheckWithAngle(boolean isFlying) {
|
||||
for (Int2ObjectMap.Entry<Vector3d> attack : playerAttackQueue.int2ObjectEntrySet()) {
|
||||
PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey());
|
||||
if (reachEntity == null) continue;
|
||||
@@ -158,16 +179,33 @@ public class Reach extends Check implements PacketCheck {
|
||||
switch (result.type()) {
|
||||
case REACH -> {
|
||||
String added = ", type=" + reachEntity.type.getName().getKey();
|
||||
if (reachEntity instanceof PacketEntitySizeable sizeable) {
|
||||
added += ", size=" + sizeable.size;
|
||||
}
|
||||
flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
case HITBOX -> {
|
||||
String added = "type=" + reachEntity.type.getName().getKey();
|
||||
if (reachEntity instanceof PacketEntitySizeable sizeable) {
|
||||
added += ", size=" + sizeable.size;
|
||||
}
|
||||
player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
case WALL_HIT -> {
|
||||
String added = reachEntity.type == EntityTypes.PLAYER ? "" : "type=" + reachEntity.type.getName().getKey();
|
||||
player.checkManager.getCheck(WallHit.class).flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
case ENTITY_PIERCE -> {
|
||||
String added = reachEntity.type == EntityTypes.PLAYER ? "" : "type=" + reachEntity.type.getName().getKey();
|
||||
player.checkManager.getCheck(EntityPierce.class).flagAndAlert(result.verbose() + added);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playerAttackQueue.clear();
|
||||
// We can't use transactions for this because of this problem:
|
||||
// transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses
|
||||
if (isFlying) blocksChangedThisTick.clear();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -196,33 +234,20 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
|
||||
// https://bugs.mojang.com/browse/MC-67665
|
||||
List<Vector3dm> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.yaw, player.pitch)));
|
||||
|
||||
// If we are a tick behind, we don't know their next look so don't bother doing this
|
||||
if (!isPrediction) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(player, player.lastYaw, player.pitch));
|
||||
|
||||
// 1.9+ players could be a tick behind because we don't get skipped ticks
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(player, player.lastYaw, player.lastPitch));
|
||||
}
|
||||
|
||||
// 1.7 players do not have any of these issues! They are always on the latest look vector
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
|
||||
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.yaw, player.pitch));
|
||||
}
|
||||
}
|
||||
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
|
||||
// We only need to check for blocking intersections for these
|
||||
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights = new ArrayList<>();
|
||||
|
||||
final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
|
||||
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
|
||||
final double distance = maxReach + 3;
|
||||
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
|
||||
final Vector3dm[] possibleLookDirs = player.getPossibleLookVectors(isPrediction);
|
||||
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
|
||||
for (Vector3dm lookVec : possibleLookDirs) {
|
||||
for (double eye : possibleEyeHeights) {
|
||||
eyePos.setY(from.getY() + eye);
|
||||
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance));
|
||||
Vector3dm endReachPos = eyePos.clone().add(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance);
|
||||
|
||||
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
|
||||
|
||||
@@ -233,13 +258,44 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
if (intercept != null) {
|
||||
minDistance = Math.min(eyePos.distance(intercept), minDistance);
|
||||
lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hitboxDebuggingEnabled())
|
||||
sendHitboxDebugData(reachEntity, from, lookVecsAndEyeHeights, isPrediction);
|
||||
|
||||
HitData foundHitData = null;
|
||||
// If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case)
|
||||
// Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check.
|
||||
if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) {
|
||||
// we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target...
|
||||
// I'm too lazy to do that and we don't need to optimize that much yet so...
|
||||
final @Nullable Pair<Double, HitData> hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, from);
|
||||
HitData hitData = hitResult.second();
|
||||
// If the returned hit result was NOT the target entity we flag the check
|
||||
if (hitData instanceof EntityHitData &&
|
||||
player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) {
|
||||
minDistance = Double.MIN_VALUE;
|
||||
foundHitData = hitData;
|
||||
// until I fix block modeling exempt any blocks changed this tick
|
||||
} else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).position())) {
|
||||
minDistance = Double.MIN_VALUE;
|
||||
foundHitData = hitData;
|
||||
}
|
||||
}
|
||||
|
||||
// if the entity is not exempt and the entity is alive
|
||||
if ((!blacklisted.contains(reachEntity.type) && reachEntity.isLivingEntity) || reachEntity.type == EntityTypes.END_CRYSTAL) {
|
||||
if (minDistance == Double.MAX_VALUE) {
|
||||
if (minDistance == Double.MIN_VALUE && foundHitData != null) {
|
||||
cancelBuffer = 1;
|
||||
if (foundHitData instanceof BlockHitData) {
|
||||
return new CheckResult(ResultType.WALL_HIT, "Hit block=" + ((BlockHitData) foundHitData).state().getType().getName() + " ");
|
||||
} else { // entity hit data
|
||||
return new CheckResult(ResultType.ENTITY_PIERCE, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().type.getName() + " ");
|
||||
}
|
||||
} else if (minDistance == Double.MAX_VALUE) {
|
||||
cancelBuffer = 1;
|
||||
return new CheckResult(ResultType.HITBOX, "");
|
||||
} else if (minDistance > maxReach) {
|
||||
@@ -255,12 +311,13 @@ public class Reach extends Check implements PacketCheck {
|
||||
|
||||
@Override
|
||||
public void onReload(ConfigManager config) {
|
||||
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
|
||||
this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true);
|
||||
this.threshold = config.getDoubleElse("Reach.threshold", 0.0005);
|
||||
}
|
||||
|
||||
private enum ResultType {
|
||||
REACH, HITBOX, NONE
|
||||
REACH, HITBOX, WALL_HIT, ENTITY_PIERCE, NONE
|
||||
}
|
||||
|
||||
private record CheckResult(ResultType type, String verbose) {
|
||||
@@ -268,4 +325,68 @@ public class Reach extends Check implements PacketCheck {
|
||||
return type != ResultType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) {
|
||||
if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this
|
||||
// Only do this for nearby blocks
|
||||
if (new Vector3dm(vector3i.x, vector3i.y, vector3i.z).distanceSquared(new Vector3dm(player.x, player.y, player.z)) > 6) return;
|
||||
// Only do this if the state really had any world impact
|
||||
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
|
||||
blocksChangedThisTick.add(vector3i);
|
||||
}
|
||||
|
||||
private boolean hitboxDebuggingEnabled() {
|
||||
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
|
||||
}
|
||||
|
||||
private void sendHitboxDebugData(PacketEntity reachEntity, Vector3d from, List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
|
||||
Map<Integer, CollisionBox> hitboxes = new HashMap<>();
|
||||
for (Int2ObjectMap.Entry<PacketEntity> entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) {
|
||||
PacketEntity entity = entry.getValue();
|
||||
if (!entity.canHit()) continue;
|
||||
|
||||
CollisionBox box;
|
||||
|
||||
if (entity.equals(reachEntity)) {
|
||||
// Target entity gets expanded hitbox
|
||||
box = entity.getPossibleCollisionBoxes();
|
||||
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
|
||||
sBox.expand(threshold);
|
||||
|
||||
// Add movement threshold uncertainty for 1.9+ or non-position updates
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
sBox.expand(player.getMovementThreshold());
|
||||
}
|
||||
} else {
|
||||
// Non-target entities
|
||||
box = entity.getMinimumPossibleCollisionBoxes();
|
||||
if (box instanceof NoCollisionBox) {
|
||||
hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE);
|
||||
continue;
|
||||
} else if (box instanceof SimpleCollisionBox sBox) {
|
||||
sBox.expand(-threshold);
|
||||
// Shrink non-target entities by movement threshold when applicable
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
sBox.expand(-player.getMovementThreshold());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add 1.8 and below extra hitbox size
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
|
||||
&& box instanceof SimpleCollisionBox) {
|
||||
((SimpleCollisionBox) box).expand(0.1f);
|
||||
}
|
||||
|
||||
hitboxes.put(entry.getIntKey(), box);
|
||||
}
|
||||
|
||||
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
|
||||
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
|
||||
lookVecsAndEyeHeights,
|
||||
new Vector3dm(from.getX(), from.getY(), from.getZ()),
|
||||
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ac.grim.grimac.checks.impl.combat;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
@CheckData(name = "WallHit", configName = "WallHit", setback = 20)
|
||||
public class WallHit extends Check implements PacketCheck {
|
||||
public WallHit(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ 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", experimental = true)
|
||||
@CheckData(name = "ElytraA", description = "Started gliding while already gliding")
|
||||
public class ElytraA extends Check implements PostPredictionCheck {
|
||||
private boolean setback;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
|
||||
@CheckData(name = "ElytraB", description = "Started gliding without jumping", experimental = true)
|
||||
@CheckData(name = "ElytraB", description = "Started gliding without jumping")
|
||||
public class ElytraB extends Check implements PostPredictionCheck {
|
||||
private boolean glide;
|
||||
private boolean setback;
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
|
||||
@CheckData(name = "ElytraC", description = "Started gliding too frequently", experimental = true)
|
||||
@CheckData(name = "ElytraC", description = "Started gliding too frequently")
|
||||
public class ElytraC extends Check implements PostPredictionCheck {
|
||||
private boolean glideThisTick, glideLastTick, setback;
|
||||
private int flags;
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 = "ElytraF", description = "Started gliding while on ground", experimental = true)
|
||||
@CheckData(name = "ElytraF", description = "Started gliding while on ground")
|
||||
public class ElytraF extends Check implements PostPredictionCheck {
|
||||
private boolean setback;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ExploitA extends Check implements PacketCheck {
|
||||
if (event.getPacketType() == PacketType.Play.Client.PLUGIN_MESSAGE && PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_13)) {
|
||||
WrapperPlayClientPluginMessage wrapper = new WrapperPlayClientPluginMessage(event);
|
||||
if (wrapper.getChannelName().equals("MC|ItemName")) {
|
||||
check(PayloadItemName.CODEC.read(wrapper.getData()).itemName(), event);
|
||||
check(new PayloadItemName(wrapper.getData()).itemName(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public class ExploitB extends Check implements PacketCheck {
|
||||
}
|
||||
}
|
||||
|
||||
ItemStack newBook = PayloadBookEdit.CODEC.read(wrapper.getData()).itemStack();
|
||||
ItemStack newBook = new PayloadBookEdit(wrapper.getData()).itemStack();
|
||||
checkItemStack(newBook, signing, event);
|
||||
oldBook = null;
|
||||
}
|
||||
@@ -287,13 +287,14 @@ public class ExploitB extends Check implements PacketCheck {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (containsInvalidCharacters(page, true)) {
|
||||
if (flagAndAlert("invalid page characters") && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// commented out because existing characters are not considered
|
||||
// if (containsInvalidCharacters(page, true)) {
|
||||
// if (flagAndAlert("invalid page characters") && shouldModifyPackets()) {
|
||||
// event.setCancelled(true);
|
||||
// player.onPacketCancel();
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
|
||||
|
||||
@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open")
|
||||
public class InventoryA extends InventoryCheck {
|
||||
public InventoryA(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
|
||||
WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
|
||||
|
||||
if (wrapper.getAction() != InteractAction.ATTACK) return;
|
||||
|
||||
// Is not possible to attack while the inventory is open.
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission())
|
||||
closeInventory();
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
|
||||
|
||||
@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open")
|
||||
public class InventoryB extends InventoryCheck {
|
||||
public InventoryB(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) {
|
||||
if (wrapper.getAction() != DiggingAction.START_DIGGING) return;
|
||||
|
||||
// Is not possible to start digging a block while the inventory is open.
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
|
||||
|
||||
@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open")
|
||||
public class InventoryC extends InventoryCheck {
|
||||
|
||||
public InventoryC(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
public void onBlockPlace(final BlockPlace place) {
|
||||
// It is not possible to place a block while the inventory is open
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert()) {
|
||||
if (shouldModifyPackets()) {
|
||||
place.resync();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
|
||||
import ac.grim.grimac.utils.data.VectorData;
|
||||
import ac.grim.grimac.utils.data.VectorData.MoveVectorData;
|
||||
import ac.grim.grimac.utils.data.VehicleData;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@CheckData(name = "InventoryD", setback = 1, decay = 0.25)
|
||||
public class InventoryD extends InventoryCheck {
|
||||
private int horseJumpVerbose;
|
||||
|
||||
public InventoryD(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionComplete(final PredictionComplete predictionComplete) {
|
||||
if (!predictionComplete.isChecked() ||
|
||||
predictionComplete.getData().isTeleport() ||
|
||||
player.getSetbackTeleportUtil().blockOffsets ||
|
||||
player.packetStateData.lastPacketWasTeleport ||
|
||||
player.packetStateData.isSlowedByUsingItem() ||
|
||||
System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.hasInventoryOpen) {
|
||||
boolean inVehicle = player.inVehicle();
|
||||
boolean isJumping, isMoving;
|
||||
|
||||
if (inVehicle) {
|
||||
VehicleData vehicle = player.vehicleData;
|
||||
|
||||
// Will flag once if player open anything with pressed space bar
|
||||
isJumping = vehicle.nextHorseJump > 0 && horseJumpVerbose++ >= 1;
|
||||
isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0;
|
||||
} else {
|
||||
MoveVectorData move = findMovement(player.predictedVelocity);
|
||||
|
||||
isJumping = player.predictedVelocity.isJump();
|
||||
isMoving = move != null && (move.x != 0 || move.z != 0);
|
||||
}
|
||||
|
||||
if (!isMoving && !isJumping) {
|
||||
reward();
|
||||
return;
|
||||
}
|
||||
|
||||
if (flag()) {
|
||||
if (!isNoSetbackPermission())
|
||||
closeInventory();
|
||||
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
|
||||
if (isMoving) joiner.add("moving");
|
||||
if (isJumping) joiner.add("jumping");
|
||||
if (inVehicle) joiner.add("inVehicle");
|
||||
|
||||
alert(joiner.toString());
|
||||
}
|
||||
} else {
|
||||
horseJumpVerbose = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private MoveVectorData findMovement(VectorData vectorData) {
|
||||
if (vectorData instanceof MoveVectorData) {
|
||||
return (MoveVectorData) vectorData;
|
||||
}
|
||||
|
||||
while (vectorData != null) {
|
||||
vectorData = vectorData.lastVector;
|
||||
if (vectorData instanceof MoveVectorData) {
|
||||
return (MoveVectorData) vectorData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open")
|
||||
public class InventoryE extends InventoryCheck {
|
||||
private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID
|
||||
|
||||
public InventoryE(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
|
||||
// It is not possible to change hotbar slots with held item change while the inventory is open
|
||||
// A container click packet would be sent instead
|
||||
if (player.hasInventoryOpen) {
|
||||
if (this.lastTransaction < player.lastTransactionReceived.get()
|
||||
&& flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
player.inventory.needResend = true;
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) {
|
||||
this.lastTransaction = player.lastTransactionSent.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
|
||||
@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true)
|
||||
public class InventoryF extends InventoryCheck {
|
||||
|
||||
public InventoryF(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
// Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted
|
||||
// once we are ahead of ViaVersion
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|
||||
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return;
|
||||
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
|
||||
if (flagAndAlert()) {
|
||||
// Cancel the packet
|
||||
if (shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (!isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ac.grim.grimac.checks.impl.inventory;
|
||||
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.InventoryCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
|
||||
|
||||
@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true)
|
||||
public class InventoryG extends InventoryCheck {
|
||||
|
||||
public InventoryG(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (player.packetStateData.lastPacketWasTeleport) return;
|
||||
super.onPacketReceive(event);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
|
||||
WrapperPlayClientEntityAction.Action action = wrapper.getAction();
|
||||
|
||||
if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING
|
||||
|| action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.hasInventoryOpen) {
|
||||
if (flagAndAlert() && !isNoSetbackPermission()) {
|
||||
closeInventory();
|
||||
}
|
||||
} else {
|
||||
reward();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving", experimental = true)
|
||||
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving")
|
||||
public class MultiActionsC extends Check implements PacketCheck {
|
||||
public MultiActionsC(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
// TODO: move this to a bett spot? not sure where to put this
|
||||
@Contract(pure = true)
|
||||
public static String getVerbose(@NotNull GrimPlayer player) {
|
||||
StringJoiner verbose = new StringJoiner(", ");
|
||||
|
||||
@@ -7,7 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
|
||||
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving", experimental = true)
|
||||
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving")
|
||||
public class MultiActionsD extends Check implements PacketCheck {
|
||||
public MultiActionsD(GrimPlayer player) {
|
||||
super(player);
|
||||
|
||||
@@ -133,8 +133,14 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleListener(GrimPlayer player) {
|
||||
if (!listeners.remove(player)) listeners.add(player);
|
||||
public boolean toggleListener(GrimPlayer player) {
|
||||
boolean wasPresent = listeners.remove(player);
|
||||
if (wasPresent) {
|
||||
return false;
|
||||
} else {
|
||||
listeners.add(player);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,9 +30,8 @@ public class FarPlace extends BlockPlaceCheck {
|
||||
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
|
||||
for (double d : possibleEyeHeights) {
|
||||
SimpleCollisionBox box = new SimpleCollisionBox(blockPos);
|
||||
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
|
||||
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
|
||||
min = Math.min(min, eyes.distanceSquared(best));
|
||||
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
|
||||
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
|
||||
}
|
||||
|
||||
// getPickRange() determines this?
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package ac.grim.grimac.checks.impl.velocity;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
@@ -46,9 +45,6 @@ public class KnockbackHandler extends Check implements PostPredictionCheck {
|
||||
WrapperPlayServerEntityVelocity velocity = new WrapperPlayServerEntityVelocity(event);
|
||||
int entityId = velocity.getEntityId();
|
||||
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
// Detect whether this knockback packet affects the player or if it is useless
|
||||
// Mojang sends extra useless knockback packets for no apparent reason
|
||||
if (player.compensatedEntities.serverPlayerVehicle != null && entityId != player.compensatedEntities.serverPlayerVehicle) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package ac.grim.grimac.checks.type;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow;
|
||||
import org.jetbrains.annotations.MustBeInvokedByOverriders;
|
||||
|
||||
public class InventoryCheck extends BlockPlaceCheck implements PacketCheck {
|
||||
// Impossible transaction ID
|
||||
protected static final long NONE = Long.MAX_VALUE;
|
||||
|
||||
protected long closeTransaction = NONE;
|
||||
protected int closePacketsToSkip;
|
||||
|
||||
public InventoryCheck(GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@MustBeInvokedByOverriders
|
||||
public void onPacketReceive(final PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
// Disallow any clicks if inventory is closing
|
||||
if (closeTransaction != NONE && shouldModifyPackets()) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
player.inventory.needResend = true;
|
||||
}
|
||||
} else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
// Players with high ping can close inventory faster than send transaction back
|
||||
if (closeTransaction != NONE && closePacketsToSkip-- <= 0) {
|
||||
closeTransaction = NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void closeInventory() {
|
||||
if (closeTransaction != NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
int windowId = player.inventory.openWindowID;
|
||||
|
||||
player.user.writePacket(new WrapperPlayServerCloseWindow(windowId));
|
||||
|
||||
// Force close inventory on server side
|
||||
closePacketsToSkip = 1; // Sending close packet to itself, so skip it
|
||||
PacketEvents.getAPI().getProtocolManager().receivePacket(
|
||||
player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId)
|
||||
);
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
int transaction = player.lastTransactionSent.get();
|
||||
closeTransaction = transaction;
|
||||
player.latencyUtils.addRealTimeTask(transaction, () -> {
|
||||
if (closeTransaction == transaction) {
|
||||
closeTransaction = NONE;
|
||||
}
|
||||
});
|
||||
|
||||
player.user.flushPackets();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.grim.grimac.command.commands;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.command.BuildableCommand;
|
||||
import ac.grim.grimac.platform.api.command.PlayerSelector;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
@@ -36,9 +37,16 @@ public class GrimDebug implements BuildableCommand {
|
||||
.required("target", GrimAPI.INSTANCE.getCommandAdapter().singlePlayerSelectorParser())
|
||||
.handler(this::handleConsoleDebug);
|
||||
|
||||
Command.Builder<Sender> hitboxDebugCommand = grimCommand
|
||||
.literal("hitboxdebug", Description.of("Toggle hitbox debug visualization"))
|
||||
.permission("grim.hitboxdebug")
|
||||
.optional("target", GrimAPI.INSTANCE.getCommandAdapter().singlePlayerSelectorParser(), Description.of("Player to debug (defaults to self if sender is player)"))
|
||||
.handler(this::handleHitboxDebug);
|
||||
|
||||
// Register command
|
||||
commandManager.command(debugCommand);
|
||||
commandManager.command(consoleDebugCommand);
|
||||
commandManager.command(hitboxDebugCommand);
|
||||
}
|
||||
|
||||
private void handleDebug(@NotNull CommandContext<Sender> context) {
|
||||
@@ -88,6 +96,48 @@ public class GrimDebug implements BuildableCommand {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
|
||||
private void handleHitboxDebug(@NotNull CommandContext<Sender> context) {
|
||||
Sender sender = context.sender();
|
||||
PlayerSelector playerSelector = context.getOrDefault("target", null);
|
||||
|
||||
// Hitbox debug requires a *player* to be the listener (the one seeing the boxes)
|
||||
if (!sender.isPlayer()) {
|
||||
sender.sendMessage(MessageUtil.getParsedComponent(sender,
|
||||
"hitboxdebug-player-only",
|
||||
"%prefix% &cHitbox debug can only be toggled by players.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the target player whose hitboxes are being debugged
|
||||
GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer());
|
||||
if (targetGrimPlayer == null) return;
|
||||
|
||||
// Get the sender's GrimPlayer data, as they are the listener
|
||||
GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId());
|
||||
if (senderGrimPlayer == null) {
|
||||
sender.sendMessage(Component.text("Could not find your player data to register as a listener.", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the HitboxDebugHandler check instance and toggle the listener
|
||||
HitboxDebugHandler hitboxHandler = targetGrimPlayer.checkManager.getCheck(HitboxDebugHandler.class);
|
||||
if (hitboxHandler == null) {
|
||||
sender.sendMessage(Component.text("HitboxDebugHandler check not found for target player.", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean enabled = hitboxHandler.toggleListener(senderGrimPlayer); // Pass the sender/listener
|
||||
|
||||
// Send feedback message
|
||||
Component message = Component.text()
|
||||
.append(Component.text("Hitbox debug listener for ", NamedTextColor.GRAY))
|
||||
.append(Component.text(targetGrimPlayer.getName(), NamedTextColor.WHITE))
|
||||
.append(Component.text(enabled ? " enabled." : " disabled.", NamedTextColor.GRAY))
|
||||
.build();
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
|
||||
private @Nullable GrimPlayer parseTarget(@NotNull Sender sender, @Nullable Sender t) {
|
||||
if (sender.isConsole() && t == null) {
|
||||
sender.sendMessage(MessageUtil.getParsedComponent(sender, "console-specify-target", "%prefix% &cYou must specify a target as the console!"));
|
||||
|
||||
@@ -7,7 +7,7 @@ import ac.grim.grimac.platform.api.sender.Sender;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.common.PropertiesUtil;
|
||||
import ac.grim.grimac.utils.reflection.ReflectionUtils;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -5,6 +5,8 @@ import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.update.*;
|
||||
import ac.grim.grimac.utils.blockplace.BlockPlaceResult;
|
||||
import ac.grim.grimac.utils.blockplace.ConsumesBlockPlace;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.BlockPlaceSnapshot;
|
||||
import ac.grim.grimac.utils.change.BlockModification;
|
||||
import ac.grim.grimac.utils.data.*;
|
||||
import ac.grim.grimac.utils.inventory.Inventory;
|
||||
@@ -56,7 +58,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
private static void placeWaterLavaSnowBucket(GrimPlayer player, ItemStack held, StateType toPlace, InteractionHand hand, int sequence) {
|
||||
HitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true);
|
||||
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, StateTypes.AIR, false, true, true);
|
||||
if (data != null) {
|
||||
BlockPlace blockPlace = new BlockPlace(player, hand, data.position(), data.closestDirection().getFaceValue(), data.closestDirection(), held, data, sequence);
|
||||
|
||||
@@ -272,7 +274,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
private static void placeBucket(GrimPlayer player, InteractionHand hand, int sequence) {
|
||||
HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
|
||||
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
|
||||
|
||||
if (data != null) {
|
||||
BlockPlace blockPlace = new BlockPlace(player, hand, data.position(), data.closestDirection().getFaceValue(), data.closestDirection(), ItemStack.EMPTY, data, sequence);
|
||||
@@ -351,7 +353,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
}
|
||||
|
||||
private static void placeLilypad(GrimPlayer player, InteractionHand hand, int sequence) {
|
||||
HitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
|
||||
BlockHitData data = WorldRayTrace.getNearestBlockHitResult(player, null, true, false, true);
|
||||
|
||||
if (data != null) {
|
||||
// A lilypad cannot replace a fluid
|
||||
|
||||
@@ -10,7 +10,7 @@ import ac.grim.grimac.utils.data.TrackerData;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntityHook;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntityTrackXRot;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package ac.grim.grimac.events.packets;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus.Action;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
|
||||
|
||||
public class PacketPlayerWindow extends PacketListenerAbstract {
|
||||
|
||||
public PacketPlayerWindow() {
|
||||
super(PacketListenerPriority.LOWEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !event.isCancelled()) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
if (player.hasInventoryOpen && isNearNetherPortal(player)) {
|
||||
handleInventoryClose(player, InventoryDesyncStatus.NETHER_PORTAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Client Status is sent in 1.7-1.8
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS) {
|
||||
WrapperPlayClientClientStatus wrapper = new WrapperPlayClientClientStatus(event);
|
||||
|
||||
if (wrapper.getAction() == Action.OPEN_INVENTORY_ACHIEVEMENT) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to do this due to 1.9 not sending anymore the inventory action in the Client Status
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
// TODO: Remove this check after we finish the before ViaVersion injection
|
||||
// Explanation: On 1.7 and 1.8 we have OPEN_INVENTORY_ACHIEVEMENT on CLIENT_STATUS packet
|
||||
// but after Via translation this information gets lost
|
||||
// This is a workaround to atleast make our inventory checks work "decently" in 1.8 clients for 1.9+ servers
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|
||||
&& player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) {
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
|
||||
if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) {
|
||||
handleInventoryOpen(player);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.RESPAWN) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
|
||||
WrapperPlayServerOpenWindow wrapper = new WrapperPlayServerOpenWindow(event);
|
||||
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
String legacyType = wrapper.getLegacyType();
|
||||
int modernType = wrapper.getType();
|
||||
InventoryDesyncStatus inventoryDesyncStatus = getContainerDesyncStatus(player, legacyType, modernType);
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> {
|
||||
if (inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
|
||||
handleInventoryOpen(player);
|
||||
} else {
|
||||
handleInventoryClose(player, inventoryDesyncStatus);
|
||||
}
|
||||
});
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.OPEN_HORSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryOpen(player));
|
||||
} else if (event.getPacketType() == PacketType.Play.Server.CLOSE_WINDOW) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser());
|
||||
if (player == null) return;
|
||||
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(),
|
||||
() -> handleInventoryClose(player, InventoryDesyncStatus.NOT_DESYNCED));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInventoryOpen(GrimPlayer player) {
|
||||
if (!player.hasInventoryOpen) {
|
||||
player.lastInventoryOpen = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
player.hasInventoryOpen = true;
|
||||
}
|
||||
|
||||
private void handleInventoryClose(GrimPlayer player, InventoryDesyncStatus desyncStatus) {
|
||||
player.hasInventoryOpen = false;
|
||||
player.inventoryDesyncStatus = desyncStatus;
|
||||
}
|
||||
|
||||
public InventoryDesyncStatus getContainerDesyncStatus(GrimPlayer player, String legacyType, int modernType) {
|
||||
// Closing beacon with the cross button cause desync in 1.7-1.8
|
||||
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) &&
|
||||
("minecraft:beacon".equals(legacyType) || modernType == 8)) {
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.BEACON;
|
||||
}
|
||||
|
||||
if (isNearNetherPortal(player)) {
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.NETHER_PORTAL;
|
||||
}
|
||||
|
||||
return player.inventoryDesyncStatus = InventoryDesyncStatus.NOT_DESYNCED;
|
||||
}
|
||||
|
||||
public boolean isNearNetherPortal(GrimPlayer player) {
|
||||
// Going inside nether portal with opened inventory cause desync, fixed in 1.12.2
|
||||
if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_12_1) &&
|
||||
player.pointThreeEstimator.isNearNetherPortal) {
|
||||
PacketEntitySelf playerEntity = player.compensatedEntities.self;
|
||||
// Client ignore nether portal if player has passengers or riding an entity
|
||||
return !playerEntity.inVehicle() && playerEntity.passengers.isEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+17
-21
@@ -1,12 +1,17 @@
|
||||
package ac.grim.grimac.events.packets.worldreader;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.events.packets.worldreader.multiblockchange.LegacyMultiBlockChangeHandler;
|
||||
import ac.grim.grimac.events.packets.worldreader.multiblockchange.V1160MultiBlockChangeBitRepackHandler;
|
||||
import ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.chunks.Column;
|
||||
import ac.grim.grimac.utils.data.TeleportData;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
@@ -16,11 +21,15 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBl
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChangeGameState;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkData;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChunkDataBulk;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUnloadChunk;
|
||||
|
||||
public class BasePacketWorldReader extends PacketListenerAbstract {
|
||||
|
||||
private final static VersionedMultiBlockChangeHandler versionedMultiBlockChangeHandler
|
||||
= PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_16)
|
||||
? new V1160MultiBlockChangeBitRepackHandler()
|
||||
: new LegacyMultiBlockChangeHandler();
|
||||
|
||||
public BasePacketWorldReader() {
|
||||
super(PacketListenerPriority.HIGH);
|
||||
}
|
||||
@@ -161,28 +170,15 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
|
||||
player.lastTransSent + 2 < System.currentTimeMillis())
|
||||
player.sendTransaction();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockChange.getBlockId()));
|
||||
int x = blockPosition.getX();
|
||||
int y = blockPosition.getY();
|
||||
int z = blockPosition.getZ();
|
||||
int blockId = blockChange.getBlockId();
|
||||
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(x, y, z, blockId));
|
||||
}
|
||||
|
||||
public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) {
|
||||
WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event);
|
||||
|
||||
int range = 16;
|
||||
|
||||
final var blocks = multiBlockChange.getBlocks();
|
||||
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
|
||||
// Don't send a transaction unless it's within 16 blocks of the player
|
||||
if (Math.abs(blockChange.getX() - player.x) < range && Math.abs(blockChange.getY() - player.y) < range && Math.abs(blockChange.getZ() - player.z) < range && player.lastTransSent + 2 < System.currentTimeMillis()) {
|
||||
player.sendTransaction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a single runnable to prevent excessive memory use when there are lots of block changes
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
|
||||
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
|
||||
player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId());
|
||||
}
|
||||
});
|
||||
versionedMultiBlockChangeHandler.handleMultiBlockChange(player, event);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package ac.grim.grimac.events.packets.worldreader.multiblockchange;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange;
|
||||
|
||||
public class LegacyMultiBlockChangeHandler implements VersionedMultiBlockChangeHandler {
|
||||
|
||||
// TODO hande optimize Pre 1.16 code to also reduce memory usage and not use wrapper
|
||||
@Override
|
||||
public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) {
|
||||
WrapperPlayServerMultiBlockChange multiBlockChange = new WrapperPlayServerMultiBlockChange(event);
|
||||
|
||||
final var blocks = multiBlockChange.getBlocks();
|
||||
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
|
||||
// Don't send a transaction unless it's within 16 blocks of the player
|
||||
if (Math.abs(blockChange.getX() - player.x) < RANGE && Math.abs(blockChange.getY() - player.y) < RANGE && Math.abs(blockChange.getZ() - player.z) < RANGE && player.lastTransSent + TRANSACTION_COOLDOWN_MS < System.currentTimeMillis()) {
|
||||
player.sendTransaction();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a single runnable to prevent excessive memory use when there are lots of block changes
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
|
||||
for (WrapperPlayServerMultiBlockChange.EncodedBlock blockChange : blocks) {
|
||||
player.compensatedWorld.updateBlock(blockChange.getX(), blockChange.getY(), blockChange.getZ(), blockChange.getBlockId());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package ac.grim.grimac.events.packets.worldreader.multiblockchange;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Minecraft’s MultiBlockChange packet batches many block updates in one shot:
|
||||
* the server sends a 64-bit section coordinate, an optional trustEdges flag,
|
||||
* a VarInt count, then that many VarLong-encoded block changes
|
||||
* (<em>52 bits</em> of global blockStateId + <em>12 bits</em> of local X/Z/Y
|
||||
* within the 16×16×16 section).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To shrink our on-heap footprint for deferred tasks, we immediately repack
|
||||
* each VarLong into one 32-bit <code>int</code>, while still using the
|
||||
* vanilla 64-bit header for coords.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Vanilla “on-the-wire” format</h3>
|
||||
* <pre>
|
||||
* 1) sectionEncodedPosition : Long
|
||||
* 2) [trustEdges? : Boolean] // only on protocol ≤1.19.4
|
||||
* 3) recordCount : VarInt
|
||||
* 4) records[] : recordCount × VarLong
|
||||
*
|
||||
* Each VarLong is bits:
|
||||
* [63……12] blockStateId (52 bits)
|
||||
* [11……8] localX (4 bits, 0–15)
|
||||
* [7……4] localZ (4 bits, 0–15)
|
||||
* [3……0] localY (4 bits, 0–15)
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Vanilla 64-bit section header “encodedPosition”</h3>
|
||||
* <pre>
|
||||
* bits 63……42 41……20 19……0
|
||||
* ┌────────┬─────────┬────────┐
|
||||
* │ secX │ secZ │ secY │
|
||||
* │ (22b) │ (22b) │ (20b) │
|
||||
* └────────┴─────────┴────────┘
|
||||
*
|
||||
* secX = encoded >> 42
|
||||
* secZ = encoded << 22 >> 42
|
||||
* secY = encoded << 44 >> 44
|
||||
* </pre>
|
||||
*
|
||||
* <h3>Our 32-bit repacked block record (MSB → LSB)</h3>
|
||||
* <pre>
|
||||
* bits 31…17 16…12 11…8 7…4 3…0
|
||||
* ┌───────┬───────┬───────┬──────┬─────┐
|
||||
* │ state │ spare │ lX │ lZ │ lY │
|
||||
* │ (15b) │ (5b) │ (4b) │ (4b) │(4b) │
|
||||
* └───────┴───────┴───────┴──────┴─────┘
|
||||
*
|
||||
* • state = (data >>> 12) & 0x7FFF
|
||||
* • spare = bits 12–16 (unused, reserved for flags)
|
||||
* • lX = (data >>> 8) & 0xF
|
||||
* • lZ = (data >>> 4) & 0xF
|
||||
* • lY = data & 0xF
|
||||
*
|
||||
* Then pack: packed = (state << 17) | ((lX<<8)|(lZ<<4)|lY)
|
||||
* </pre>
|
||||
*/
|
||||
public final class V1160MultiBlockChangeBitRepackHandler
|
||||
implements ac.grim.grimac.events.packets.worldreader.multiblockchange.VersionedMultiBlockChangeHandler {
|
||||
|
||||
/* ---------- bit masks / shifts for the packed int ---------- */
|
||||
private static final int SHIFT_STATE = 17; // 32-15 = 17
|
||||
private static final int MASK_STATE = 0x7FFF; // 15 bits
|
||||
|
||||
static final int MASK_LOCAL = 0xFFF; // 12 bits
|
||||
|
||||
/* ---------- does this protocol still have trustEdges ? ----- */
|
||||
private static final boolean HAS_TRUST_EDGES =
|
||||
PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_19_4);
|
||||
|
||||
@Override
|
||||
public void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event) {
|
||||
// PE resets writer index for us, we don't have to call buffer.writerIndex(originalWriterIndex)
|
||||
ByteBuf buf = (ByteBuf) event.getByteBuf();
|
||||
|
||||
/* 1. Section-position header (64 bits) ------------------ */
|
||||
long sectionEncodedPosition = ByteBufHelper.readLong(buf);
|
||||
|
||||
if (HAS_TRUST_EDGES) { // skip only when it really exists
|
||||
buf.skipBytes(1);
|
||||
}
|
||||
|
||||
/* 2. Record count + packed-int array ------------------- */
|
||||
int recordCount = ByteBufHelper.readVarInt(buf);
|
||||
int[] packed = new int[recordCount];
|
||||
|
||||
/* 3. Decode packet (still comes as varLong per record) */
|
||||
// Unpack section coords once for the “near player” test
|
||||
int secX = (int) (sectionEncodedPosition >> 42);
|
||||
int secZ = (int) (sectionEncodedPosition << 22 >> 42);
|
||||
int secY = (int) (sectionEncodedPosition << 44 >> 44);
|
||||
|
||||
int baseX = secX << 4;
|
||||
int baseY = secY << 4;
|
||||
int baseZ = secZ << 4;
|
||||
|
||||
boolean sendTx = false;
|
||||
|
||||
// Use this to not spam the player with transactions if one has been sent within COOLDOWN
|
||||
long now = System.currentTimeMillis();
|
||||
for (int i = 0; i < recordCount; i++) {
|
||||
|
||||
long data = readVarLong(buf); // 52+12 bits
|
||||
|
||||
int local = (int) (data & 0xFFFL); // 12-bit pos
|
||||
|
||||
packed[i] = repackFromLong(data);
|
||||
|
||||
/* -------- near-player distance test -------------- */
|
||||
if (!sendTx) {
|
||||
int lx = (local >>> 8) & 0xF;
|
||||
int lz = (local >>> 4) & 0xF;
|
||||
int ly = local & 0xF;
|
||||
|
||||
int wx = baseX + lx, wy = baseY + ly, wz = baseZ + lz;
|
||||
|
||||
if (Math.abs(wx - player.x) < RANGE &&
|
||||
Math.abs(wy - player.y) < RANGE &&
|
||||
Math.abs(wz - player.z) < RANGE &&
|
||||
player.lastTransSent + TRANSACTION_COOLDOWN_MS < now) {
|
||||
sendTx = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sendTx)
|
||||
player.sendTransaction();
|
||||
|
||||
/* 4. Queue runnable – captures only int[] + sectionPos */
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
|
||||
|
||||
// unpack section once per execution
|
||||
int sX = (int) (sectionEncodedPosition >> 42);
|
||||
int sY = (int) (sectionEncodedPosition << 44 >> 44);
|
||||
int sZ = (int) (sectionEncodedPosition << 22 >> 42);
|
||||
|
||||
int bx = sX << 4, by = sY << 4, bz = sZ << 4;
|
||||
|
||||
for (int rec : packed) {
|
||||
int stateId = (rec >>> SHIFT_STATE) & MASK_STATE;
|
||||
int lx = (rec >>> 8) & 0xF;
|
||||
int lz = (rec >>> 4) & 0xF;
|
||||
int ly = rec & 0xF;
|
||||
|
||||
int wx = bx + lx;
|
||||
int wy = by + ly;
|
||||
int wz = bz + lz;
|
||||
|
||||
player.compensatedWorld.updateBlock(wx, wy, wz, stateId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int repackFromLong(long data) {
|
||||
// 1) extract the 15-bit state from bits 12.. (original >> 12)
|
||||
int blockState = (int)((data >>> 12) & MASK_STATE);
|
||||
|
||||
// 2) extract the 12-bit local from bits 0..11
|
||||
int local = (int)( data & MASK_LOCAL);
|
||||
|
||||
// 3) glue them together
|
||||
return (blockState << SHIFT_STATE) | local;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package ac.grim.grimac.events.packets.worldreader.multiblockchange;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface VersionedMultiBlockChangeHandler {
|
||||
|
||||
int RANGE = 16;
|
||||
long TRANSACTION_COOLDOWN_MS = 2; // In milliseconds
|
||||
|
||||
void handleMultiBlockChange(GrimPlayer player, PacketSendEvent event);
|
||||
default long readVarLong(ByteBuf buf) {
|
||||
long value = 0;
|
||||
int size = 0;
|
||||
int b;
|
||||
while (((b = buf.readByte()) & 0x80) == 0x80) {
|
||||
value |= (long) (b & 0x7F) << (size++ * 7);
|
||||
}
|
||||
return value | ((long) (b & 0x7F) << (size * 7));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.AbstractCheck;
|
||||
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
|
||||
import ac.grim.grimac.checks.impl.aim.AimDuplicateLook;
|
||||
import ac.grim.grimac.checks.impl.aim.AimModulo360;
|
||||
import ac.grim.grimac.checks.impl.aim.processor.AimProcessor;
|
||||
@@ -11,15 +12,13 @@ import ac.grim.grimac.checks.impl.chat.ChatA;
|
||||
import ac.grim.grimac.checks.impl.chat.ChatB;
|
||||
import ac.grim.grimac.checks.impl.chat.ChatC;
|
||||
import ac.grim.grimac.checks.impl.chat.ChatD;
|
||||
import ac.grim.grimac.checks.impl.combat.Hitboxes;
|
||||
import ac.grim.grimac.checks.impl.combat.MultiInteractA;
|
||||
import ac.grim.grimac.checks.impl.combat.MultiInteractB;
|
||||
import ac.grim.grimac.checks.impl.combat.Reach;
|
||||
import ac.grim.grimac.checks.impl.combat.*;
|
||||
import ac.grim.grimac.checks.impl.crash.*;
|
||||
import ac.grim.grimac.checks.impl.elytra.*;
|
||||
import ac.grim.grimac.checks.impl.exploit.ExploitA;
|
||||
import ac.grim.grimac.checks.impl.exploit.ExploitB;
|
||||
import ac.grim.grimac.checks.impl.groundspoof.NoFall;
|
||||
import ac.grim.grimac.checks.impl.inventory.*;
|
||||
import ac.grim.grimac.checks.impl.misc.ClientBrand;
|
||||
import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation;
|
||||
import ac.grim.grimac.checks.impl.misc.Post;
|
||||
@@ -116,6 +115,12 @@ public class CheckManager {
|
||||
.put(BadPacketsU.class, new BadPacketsU(player))
|
||||
.put(BadPacketsV.class, new BadPacketsV(player))
|
||||
.put(BadPacketsY.class, new BadPacketsY(player))
|
||||
.put(BadPacketsZ.class, new BadPacketsZ(player))
|
||||
.put(InventoryA.class, new InventoryA(player))
|
||||
.put(InventoryB.class, new InventoryB(player))
|
||||
.put(InventoryE.class, new InventoryE(player))
|
||||
.put(InventoryF.class, new InventoryF(player))
|
||||
.put(InventoryG.class, new InventoryG(player))
|
||||
.put(MultiActionsA.class, new MultiActionsA(player))
|
||||
.put(MultiActionsC.class, new MultiActionsC(player))
|
||||
.put(MultiActionsD.class, new MultiActionsD(player))
|
||||
@@ -124,7 +129,7 @@ public class CheckManager {
|
||||
.put(PacketOrderC.class, new PacketOrderC(player))
|
||||
.put(PacketOrderD.class, new PacketOrderD(player))
|
||||
.put(PacketOrderO.class, new PacketOrderO(player))
|
||||
.put(PacketOrderP.class, new PacketOrderP(player))
|
||||
// .put(PacketOrderP.class, new PacketOrderP(player))
|
||||
.put(SprintA.class, new SprintA(player))
|
||||
.put(VehicleA.class, new VehicleA(player))
|
||||
.put(VehicleB.class, new VehicleB(player))
|
||||
@@ -200,6 +205,7 @@ public class CheckManager {
|
||||
.build();
|
||||
|
||||
blockPlaceChecks = new ImmutableClassToInstanceMap.Builder<BlockPlaceCheck>()
|
||||
.put(InventoryC.class, new InventoryC(player))
|
||||
.put(InvalidPlaceA.class, new InvalidPlaceA(player))
|
||||
.put(InvalidPlaceB.class, new InvalidPlaceB(player))
|
||||
.put(AirLiquidPlace.class, new AirLiquidPlace(player))
|
||||
@@ -249,6 +255,9 @@ public class CheckManager {
|
||||
.put(TransactionOrder.class, new TransactionOrder(player))
|
||||
.put(VehicleC.class, new VehicleC(player))
|
||||
.put(Hitboxes.class, new Hitboxes(player)) // Hitboxes is invoked by Reach
|
||||
.put(WallHit.class, new WallHit(player))
|
||||
.put(EntityPierce.class, new EntityPierce(player))
|
||||
.put(HitboxDebugHandler.class, new HitboxDebugHandler(player))
|
||||
.build();
|
||||
|
||||
allChecks = new ImmutableClassToInstanceMap.Builder<AbstractCheck>()
|
||||
|
||||
@@ -100,6 +100,13 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
blockMovementsUntilResync(true, true);
|
||||
}
|
||||
|
||||
public void executeForceResyncNoSimulation() {
|
||||
if (player.gamemode == GameMode.SPECTATOR || player.disableGrim)
|
||||
return; // We don't care about spectators, they don't flag
|
||||
if (lastKnownGoodPosition == null) return; // Player hasn't spawned yet
|
||||
blockMovementsUntilResync(false, true);
|
||||
}
|
||||
|
||||
public void executeNonSimulatingSetback() {
|
||||
if (player.gamemode == GameMode.SPECTATOR || player.disableGrim)
|
||||
return; // We don't care about spectators, they don't flag
|
||||
@@ -131,9 +138,9 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
|
||||
} else if (player.wasTouchingLava) {
|
||||
vector.multiply(0.5D);
|
||||
if (player.hasGravity)
|
||||
vector.add(new Vector3dm(0.0D, -player.gravity / 4.0D, 0.0D));
|
||||
vector.add(0.0D, -player.gravity / 4.0D, 0.0D);
|
||||
} else if (player.isGliding) {
|
||||
PredictionEngineElytra.getElytraMovement(player, vector, ReachUtils.getLook(player, player.yaw, player.pitch)).multiply(player.stuckSpeedMultiplier).multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
|
||||
PredictionEngineElytra.getElytraMovement(player, vector, ReachUtils.getLook(player, player.yaw, player.pitch)).multiply(player.stuckSpeedMultiplier).multiply(0.99F, 0.98F, 0.99F);
|
||||
vector.setY(vector.getY() - 0.05); // Make the player fall a bit
|
||||
} else { // Gliding doesn't have friction, we handle it differently
|
||||
PredictionEngineNormal.staticVectorEndOfTick(player, vector); // Lava and normal movement
|
||||
|
||||
@@ -10,12 +10,14 @@ import com.github.retrooper.packetevents.protocol.item.enchantment.type.Enchantm
|
||||
import com.github.retrooper.packetevents.protocol.item.type.ItemTypes;
|
||||
import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.PEVersion;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class PacketEventsInit implements LoadableInitable {
|
||||
|
||||
private final PacketEventsAPI<?> packetEventsAPI;
|
||||
PEVersion MINIMUM_REQUIRED_PE_VERSION = new PEVersion(2, 8, 0, true);
|
||||
|
||||
public PacketEventsInit(PacketEventsAPI<?> packetEventsAPI) {
|
||||
this.packetEventsAPI = packetEventsAPI;
|
||||
@@ -25,6 +27,17 @@ public class PacketEventsInit implements LoadableInitable {
|
||||
public void load() {
|
||||
LogUtil.info("Loading PacketEvents...");
|
||||
PacketEvents.setAPI(packetEventsAPI);
|
||||
|
||||
if (!checkPacketEventsVersion()) {
|
||||
LogUtil.error("\n" +
|
||||
"******************************************************\n" +
|
||||
"GrimAC requires PacketEvents >= " + MINIMUM_REQUIRED_PE_VERSION +
|
||||
(MINIMUM_REQUIRED_PE_VERSION.snapshot() ? "-SNAPSHOT" : "") + "\n" +
|
||||
"Current version: " + PacketEvents.getAPI().getVersion() + "\n" +
|
||||
"Please update PacketEvents to a compatible version.\n" +
|
||||
"*****************************************************");
|
||||
}
|
||||
|
||||
PacketEvents.getAPI().getSettings()
|
||||
.fullStackTrace(true)
|
||||
.kickOnPacketException(true)
|
||||
@@ -44,4 +57,33 @@ public class PacketEventsInit implements LoadableInitable {
|
||||
ParticleTypes.DUST.getName();
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean checkPacketEventsVersion() {
|
||||
PEVersion current = PacketEvents.getAPI().getVersion();
|
||||
PEVersion required = MINIMUM_REQUIRED_PE_VERSION;
|
||||
|
||||
// If current version is newer, always accept
|
||||
if (current.isNewerThan(required)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If current version is exactly equal to required (including snapshot status), accept
|
||||
if (current.major() == required.major()
|
||||
&& current.minor() == required.minor()
|
||||
&& current.patch() == required.patch()
|
||||
&& current.snapshot() == required.snapshot()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If required is a snapshot, accept matching release or snapshot
|
||||
if (required.snapshot()
|
||||
&& current.major() == required.major()
|
||||
&& current.minor() == required.minor()
|
||||
&& current.patch() == required.patch()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, reject
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public class PacketManager implements StartableInitable {
|
||||
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerJoinQuit());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPingListener());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerWindow());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerDigging());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketPlayerAttack());
|
||||
PacketEvents.getAPI().getEventManager().registerListener(new PacketEntityAction());
|
||||
|
||||
@@ -2,7 +2,7 @@ package ac.grim.grimac.manager.init.start;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package ac.grim.grimac.manager.init.start;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
@@ -12,6 +12,7 @@ public class ViaVersion implements StartableInitable {
|
||||
@Override
|
||||
public void start() {
|
||||
if (!ViaVersionUtil.isAvailable) return;
|
||||
ViaVersionUtil.injectHooks();
|
||||
|
||||
ServerVersion serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
|
||||
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@ import ac.grim.grimac.platform.api.world.PlatformWorld;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
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;
|
||||
@@ -116,7 +115,7 @@ public class DefaultResyncHandler implements ResyncHandler {
|
||||
GrimAPI.INSTANCE.getScheduler().getRegionScheduler().execute(GrimAPI.INSTANCE.getGrimPlugin(), world, chunkX, chunkZ, () -> {
|
||||
if (!player.platformPlayer.isOnline() || !player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport)
|
||||
return;
|
||||
if (player.platformPlayer.getPosition().distance(new Vector3d(x, y, z)) >= 64)
|
||||
if (player.platformPlayer.distanceSquared(x, y, z) >= 64 * 64)
|
||||
return;
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) return; // Don't load chunks sync
|
||||
|
||||
|
||||
@@ -25,4 +25,6 @@ public interface GrimEntity extends GrimIdentity {
|
||||
PlatformWorld getWorld();
|
||||
|
||||
Location getLocation();
|
||||
|
||||
double distanceSquared(double x, double y, double z);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import ac.grim.grimac.utils.data.packetentity.PacketEntitySelf;
|
||||
import ac.grim.grimac.utils.data.tags.SyncedTags;
|
||||
import ac.grim.grimac.utils.enums.FluidTag;
|
||||
import ac.grim.grimac.utils.enums.Pose;
|
||||
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
|
||||
import ac.grim.grimac.utils.latency.*;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.Location;
|
||||
@@ -38,8 +39,9 @@ import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import ac.grim.grimac.utils.nmsutil.BlockProperties;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import ac.grim.grimac.utils.nmsutil.ReachUtils;
|
||||
import ac.grim.grimac.utils.nmsutil.Materials;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
@@ -209,7 +211,7 @@ public class GrimPlayer implements GrimUser {
|
||||
public final CompensatedWorld compensatedWorld;
|
||||
public final CompensatedEntities compensatedEntities;
|
||||
public final CompensatedInventory inventory;
|
||||
public final LatencyUtils latencyUtils = new LatencyUtils(this);
|
||||
public final ILatencyUtils latencyUtils = new LatencyUtils(this);
|
||||
public final PointThreeEstimator pointThreeEstimator;
|
||||
public final TrigHandler trigHandler = new TrigHandler(this);
|
||||
public final PacketStateData packetStateData = new PacketStateData();
|
||||
@@ -236,6 +238,9 @@ public class GrimPlayer implements GrimUser {
|
||||
// possibleEyeHeights[0] = Standing eye heights, [1] = Sneaking. [2] = Elytra, Swimming, and Riptide Trident which only exists in 1.9+
|
||||
public final double[][] possibleEyeHeights = new double[3][];
|
||||
public int totalFlyingPacketsSent;
|
||||
public boolean hasInventoryOpen;
|
||||
public long lastInventoryOpen;
|
||||
public InventoryDesyncStatus inventoryDesyncStatus;
|
||||
public final Queue<BlockPlaceSnapshot> placeUseItemPackets = new LinkedBlockingQueue<>();
|
||||
public final Queue<BlockBreak> queuedBreaks = new LinkedBlockingQueue<>();
|
||||
public final PlayerBlockHistory blockHistory = new PlayerBlockHistory();
|
||||
@@ -658,6 +663,33 @@ public class GrimPlayer implements GrimUser {
|
||||
}
|
||||
}
|
||||
|
||||
// 1.8-1.10.2 specific mouse delay fix (MC-67665)
|
||||
// https://bugs.mojang.com/browse/MC-67665
|
||||
// 1.9-1.21.1 specific desync due to skipped ticks
|
||||
// Players can be a tick behind on both pitch and yaw together
|
||||
// 1.21.2+ added end tick input packet, fixing skipped tick issues
|
||||
public Vector3dm[] getPossibleLookVectors(boolean isPrediction) {
|
||||
// https://bugs.mojang.com/browse/MC-67665
|
||||
List<Vector3dm> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(this, this.yaw, this.pitch)));
|
||||
|
||||
// If we are a tick behind, we don't know their next look so don't bother doing this
|
||||
if (!isPrediction) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(this, this.lastYaw, this.pitch));
|
||||
|
||||
// 1.9+ players could be a tick behind because we don't get skipped ticks
|
||||
if (this.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
|
||||
possibleLookDirs.add(ReachUtils.getLook(this, this.lastYaw, this.lastPitch));
|
||||
}
|
||||
|
||||
// 1.7 players do not have any of these issues! They are always on the latest look vector
|
||||
if (this.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
|
||||
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(this, this.yaw, this.pitch));
|
||||
}
|
||||
}
|
||||
|
||||
return possibleLookDirs.toArray(new Vector3dm[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTransactionPing() {
|
||||
return GrimMath.floor(transactionPing / 1e6);
|
||||
|
||||
@@ -70,7 +70,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
|
||||
if (!invalidVehicle && !data.isTeleport()) {
|
||||
// Teleport the player back to avoid players being able to simply ignore transactions
|
||||
player.getSetbackTeleportUtil().executeForceResync();
|
||||
// We shouldn't simulate movement in unloaded chunks
|
||||
player.getSetbackTeleportUtil().executeForceResyncNoSimulation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +197,6 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
player.vehicleData.wasVehicleSwitch = false;
|
||||
|
||||
if (riding != null) {
|
||||
Vector3dm pos = new Vector3dm(player.x, player.y, player.z);
|
||||
SimpleCollisionBox interTruePositions = riding.getPossibleCollisionBoxes();
|
||||
|
||||
// We shrink the expanded bounding box to what the packet positions can be, for a smaller box
|
||||
@@ -206,7 +206,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
interTruePositions.expand(-width, 0, -width);
|
||||
interTruePositions.expandMax(0, -height, 0);
|
||||
|
||||
Vector3dm cutTo = VectorUtils.cutBoxToVector(pos, interTruePositions);
|
||||
Vector3dm cutTo = VectorUtils.cutBoxToVector(player.x, player.y, player.z, interTruePositions);
|
||||
|
||||
// Now we need to simulate a tick starting at the most optimal position
|
||||
// The start position is never sent, so we assume the most optimal start position
|
||||
@@ -567,7 +567,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
|
||||
// Patch sprint jumping with elytra exploit
|
||||
if (player.platformPlayer != null && player.isGliding && player.predictedVelocity.isJump() && player.isSprinting && !allowSprintJumpingWithElytra) {
|
||||
SetbackTeleportUtil.SetbackPosWithVector lastKnownGoodPosition = player.getSetbackTeleportUtil().lastKnownGoodPosition;
|
||||
lastKnownGoodPosition.setVector(lastKnownGoodPosition.getVector().multiply(new Vector3dm(0.6 * 0.91, 1, 0.6 * 0.91)));
|
||||
lastKnownGoodPosition.setVector(lastKnownGoodPosition.getVector().multiply(0.6 * 0.91, 1, 0.6 * 0.91));
|
||||
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.predictionengine;
|
||||
|
||||
import ac.grim.grimac.checks.impl.combat.Reach;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
|
||||
import ac.grim.grimac.utils.collisions.CollisionData;
|
||||
@@ -21,6 +22,7 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
|
||||
import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@@ -107,6 +109,7 @@ public class PointThreeEstimator {
|
||||
private boolean isNearHorizontalFlowingLiquid = false; // We can't calculate the direction, only a toggle
|
||||
private boolean isNearVerticalFlowingLiquid = false; // We can't calculate exact values, once again a toggle
|
||||
private boolean isNearBubbleColumn = false; // We can't calculate exact values once again
|
||||
public boolean isNearNetherPortal = false; // We can't calculate exact values once again
|
||||
|
||||
private int maxPositiveLevitation = Integer.MIN_VALUE; // Positive potion effects [0, 128]
|
||||
private int minNegativeLevitation = Integer.MAX_VALUE; // Negative potion effects [-127, -1]r
|
||||
@@ -156,6 +159,8 @@ public class PointThreeEstimator {
|
||||
isNearFluid = true;
|
||||
}
|
||||
|
||||
player.checkManager.getPacketCheck(Reach.class).handleBlockChange(new Vector3i(x, y, z), state);
|
||||
|
||||
if (pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) {
|
||||
// https://github.com/MWHunter/Grim/issues/613
|
||||
int controllingEntityId = player.inVehicle() ? player.getRidingVehicleId() : player.entityID;
|
||||
@@ -266,6 +271,7 @@ public class PointThreeEstimator {
|
||||
isNearClimbable = false;
|
||||
isNearBubbleColumn = false;
|
||||
isNearFluid = false;
|
||||
isNearNetherPortal = false;
|
||||
|
||||
// Check for flowing water
|
||||
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
|
||||
@@ -287,6 +293,10 @@ public class PointThreeEstimator {
|
||||
isNearFluid = true;
|
||||
}
|
||||
|
||||
if (state.getType() == StateTypes.NETHER_PORTAL) {
|
||||
isNearNetherPortal = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,12 +157,12 @@ public class UncertaintyHandler {
|
||||
Vector3dm maxLocation = new Vector3dm(entityBox.maxX, entityBox.maxY, entityBox.maxZ);
|
||||
Vector3dm minLocation = new Vector3dm(entityBox.minX, entityBox.minY, entityBox.minZ);
|
||||
|
||||
Vector3dm diff = minLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
|
||||
Vector3dm diff = minLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
|
||||
fishingRodPullBox.minX = Math.min(0, diff.getX());
|
||||
fishingRodPullBox.minY = Math.min(0, diff.getY());
|
||||
fishingRodPullBox.minZ = Math.min(0, diff.getZ());
|
||||
|
||||
diff = maxLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
|
||||
diff = maxLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
|
||||
fishingRodPullBox.maxX = Math.max(0, diff.getX());
|
||||
fishingRodPullBox.maxY = Math.max(0, diff.getY());
|
||||
fishingRodPullBox.maxZ = Math.max(0, diff.getZ());
|
||||
|
||||
+4
-4
@@ -15,7 +15,7 @@ import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.FluidFallingAdjustedMovement;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import ac.grim.grimac.utils.nmsutil.MainSupportingBlockPosFinder;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.team.EntityPredicates;
|
||||
import ac.grim.grimac.utils.team.EntityTeam;
|
||||
import ac.grim.grimac.utils.team.TeamHandler;
|
||||
@@ -233,7 +233,7 @@ public class MovementTicker {
|
||||
player.predictedVelocity = new VectorData(collide.clone(), player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
|
||||
|
||||
float f = BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
|
||||
player.clientVelocity.multiply(new Vector3dm(f, 1, f));
|
||||
player.clientVelocity.multiply(f, 1, f);
|
||||
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)) {
|
||||
return;
|
||||
@@ -446,14 +446,14 @@ public class MovementTicker {
|
||||
|
||||
// Lava movement changed in 1.16
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16) && player.slightlyTouchingLava) {
|
||||
player.clientVelocity = player.clientVelocity.multiply(new Vector3dm(0.5D, 0.800000011920929D, 0.5D));
|
||||
player.clientVelocity = player.clientVelocity.multiply(0.5D, 0.800000011920929D, 0.5D);
|
||||
player.clientVelocity = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity);
|
||||
} else {
|
||||
player.clientVelocity.multiply(0.5D);
|
||||
}
|
||||
|
||||
if (player.hasGravity)
|
||||
player.clientVelocity.add(new Vector3dm(0.0D, -playerGravity / 4.0D, 0.0D));
|
||||
player.clientVelocity.add(0.0D, -playerGravity / 4.0D, 0.0D);
|
||||
|
||||
} else if (player.isGliding) {
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && Collisions.onClimbable(player, player.lastX, player.lastY, player.lastZ)) {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class MovementTickerStrider extends MovementTickerRideable {
|
||||
if (isAbove(player) && player.compensatedWorld.getLavaFluidLevelAt((int) Math.floor(player.x), (int) Math.floor(player.y + 1), (int) Math.floor(player.z)) == 0) {
|
||||
player.onGround = true;
|
||||
} else {
|
||||
player.clientVelocity.multiply(0.5).add(new Vector3dm(0, 0.05, 0));
|
||||
player.clientVelocity.multiply(0.5).add(0, 0.05, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-16
@@ -417,7 +417,7 @@ public class PredictionEngine {
|
||||
data.vector.setZ(data.vector.getZ() * 0.6);
|
||||
data.addVectorType(VectorData.VectorType.AttackSlow);
|
||||
} else {
|
||||
velocities.add(data.returnNewModified(data.vector.clone().multiply(new Vector3dm(0.6, 1, 0.6)), VectorData.VectorType.AttackSlow));
|
||||
velocities.add(data.returnNewModified(data.vector.clone().multiply(0.6, 1, 0.6), VectorData.VectorType.AttackSlow));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,52 +492,63 @@ public class PredictionEngine {
|
||||
// Order priority (to avoid false positives and false flagging future predictions):
|
||||
// Knockback and explosions
|
||||
// 0.03 ticks
|
||||
// Movement without input
|
||||
// Normal movement
|
||||
// First bread knockback and explosions
|
||||
// Flagging groundspoof
|
||||
// Flagging flip items
|
||||
if (a.isExplosion())
|
||||
aScore -= 5;
|
||||
aScore -= 10;
|
||||
|
||||
if (a.isKnockback())
|
||||
aScore -= 5;
|
||||
aScore -= 10;
|
||||
|
||||
if (b.isExplosion())
|
||||
bScore -= 5;
|
||||
bScore -= 10;
|
||||
|
||||
if (b.isKnockback())
|
||||
bScore -= 5;
|
||||
bScore -= 10;
|
||||
|
||||
if (a.isFirstBreadExplosion())
|
||||
aScore += 1;
|
||||
aScore += 2;
|
||||
|
||||
if (b.isFirstBreadExplosion())
|
||||
bScore += 1;
|
||||
bScore += 2;
|
||||
|
||||
if (a.isFirstBreadKb())
|
||||
aScore += 1;
|
||||
aScore += 2;
|
||||
|
||||
if (b.isFirstBreadKb())
|
||||
bScore += 1;
|
||||
bScore += 2;
|
||||
|
||||
if (a.isFlipItem())
|
||||
aScore += 3;
|
||||
aScore += 6;
|
||||
|
||||
if (b.isFlipItem())
|
||||
bScore += 3;
|
||||
bScore += 6;
|
||||
|
||||
if (a.isZeroPointZeroThree())
|
||||
aScore -= 1;
|
||||
aScore -= 2;
|
||||
|
||||
if (b.isZeroPointZeroThree())
|
||||
bScore -= 2;
|
||||
|
||||
if (a.isWithInput() || a.isJump())
|
||||
aScore += 1;
|
||||
else
|
||||
aScore -= 1;
|
||||
|
||||
if (b.isWithInput() || b.isJump())
|
||||
bScore += 1;
|
||||
else
|
||||
bScore -= 1;
|
||||
|
||||
// If the player is on the ground but the vector leads the player off the ground
|
||||
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
|
||||
aScore += 2;
|
||||
aScore += 4;
|
||||
|
||||
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
|
||||
bScore += 2;
|
||||
bScore += 4;
|
||||
|
||||
if (aScore != bScore)
|
||||
return Integer.compare(aScore, bScore);
|
||||
@@ -829,9 +840,9 @@ public class PredictionEngine {
|
||||
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
|
||||
|
||||
Vector3dm input = transformInputsToVector(player, new Vector3dm(strafe, 0, forward));
|
||||
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
|
||||
VectorData result = new VectorData.MoveVectorData(possibleLastTickOutput.vector.clone()
|
||||
.add(getMovementResultFromInput(player, input, speed, player.yaw)),
|
||||
possibleLastTickOutput, VectorData.VectorType.InputResult);
|
||||
possibleLastTickOutput, VectorData.VectorType.InputResult, forward, strafe);
|
||||
result.input = input;
|
||||
if (applyStuckSpeed != 0) {
|
||||
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
|
||||
|
||||
+5
-5
@@ -31,24 +31,24 @@ public class PredictionEngineElytra extends PredictionEngine {
|
||||
recalculatedGravity = player.getClientVersion().isOlderThan(ClientVersion.V_1_20_5) ? 0.01 : Math.min(recalculatedGravity, 0.01);
|
||||
}
|
||||
|
||||
vector.add(new Vector3dm(0.0D, recalculatedGravity * (-1.0D + vertCosRotation * 0.75D), 0.0D));
|
||||
vector.add(0.0D, recalculatedGravity * (-1.0D + vertCosRotation * 0.75D), 0.0D);
|
||||
double d5;
|
||||
|
||||
// Handle slowing the player down when falling
|
||||
if (vector.getY() < 0.0D && horizontalSqrt > 0.0D) {
|
||||
d5 = vector.getY() * -0.1D * vertCosRotation;
|
||||
vector.add(new Vector3dm(lookVector.getX() * d5 / horizontalSqrt, d5, lookVector.getZ() * d5 / horizontalSqrt));
|
||||
vector.add(lookVector.getX() * d5 / horizontalSqrt, d5, lookVector.getZ() * d5 / horizontalSqrt);
|
||||
}
|
||||
|
||||
// Handle accelerating the player when they are looking down
|
||||
if (pitchRadians < 0.0F && horizontalSqrt > 0.0D) {
|
||||
d5 = horizontalLength * (double) (-player.trigHandler.sin(pitchRadians)) * 0.04D;
|
||||
vector.add(new Vector3dm(-lookVector.getX() * d5 / horizontalSqrt, d5 * 3.2D, -lookVector.getZ() * d5 / horizontalSqrt));
|
||||
vector.add(-lookVector.getX() * d5 / horizontalSqrt, d5 * 3.2D, -lookVector.getZ() * d5 / horizontalSqrt);
|
||||
}
|
||||
|
||||
// Handle accelerating the player sideways
|
||||
if (horizontalSqrt > 0) {
|
||||
vector.add(new Vector3dm((lookVector.getX() / horizontalSqrt * horizontalLength - vector.getX()) * 0.1D, 0.0D, (lookVector.getZ() / horizontalSqrt * horizontalLength - vector.getZ()) * 0.1D));
|
||||
vector.add((lookVector.getX() / horizontalSqrt * horizontalLength - vector.getX()) * 0.1D, 0.0D, (lookVector.getZ() / horizontalSqrt * horizontalLength - vector.getZ()) * 0.1D);
|
||||
}
|
||||
|
||||
return vector;
|
||||
@@ -67,7 +67,7 @@ public class PredictionEngineElytra extends PredictionEngine {
|
||||
for (VectorData data : possibleVectors) {
|
||||
Vector3dm elytraResult = getElytraMovement(player, data.vector.clone(), currentLook);
|
||||
if (applyStuckSpeed != 0) elytraResult.multiply(player.stuckSpeedMultiplier);
|
||||
elytraResult.multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
|
||||
elytraResult.multiply(0.99F, 0.98F, 0.99F);
|
||||
VectorData modified = data.returnNewModified(elytraResult, VectorData.VectorType.InputResult);
|
||||
modified.input = new Vector3dm(0, 0, 0);
|
||||
results.add(modified);
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ public class PredictionEngineLava extends PredictionEngine {
|
||||
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
|
||||
} else {
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.04f, 0), vector, VectorData.VectorType.Jump));
|
||||
}
|
||||
|
||||
if (player.slightlyTouchingLava && player.lastOnGround && !player.onGround) {
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class PredictionEngineNormal extends PredictionEngine {
|
||||
|
||||
JumpPower.jumpFromGround(player, jump);
|
||||
} else {
|
||||
jump.add(new Vector3dm(0, player.flySpeed * 3, 0));
|
||||
jump.add(0, player.flySpeed * 3, 0);
|
||||
if (!player.wasFlying) {
|
||||
Vector3dm edgeCaseJump = jump.clone();
|
||||
JumpPower.jumpFromGround(player, edgeCaseJump);
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ public class PredictionEngineWater extends PredictionEngine {
|
||||
private float swimmingFriction;
|
||||
|
||||
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector, float swimmingFriction, double playerGravity, boolean isFalling) {
|
||||
vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
|
||||
vector.multiply(swimmingFriction, 0.8F, swimmingFriction);
|
||||
Vector3dm fluidVector = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, vector);
|
||||
vector.setX(fluidVector.getX());
|
||||
vector.setY(fluidVector.getY());
|
||||
@@ -76,7 +76,7 @@ public class PredictionEngineWater extends PredictionEngine {
|
||||
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
|
||||
} else {
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.04f, 0), vector, VectorData.VectorType.Jump));
|
||||
}
|
||||
|
||||
if (player.slightlyTouchingWater && player.lastOnGround && !player.onGround) {
|
||||
|
||||
+3
-3
@@ -47,10 +47,10 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
|
||||
@Override
|
||||
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
|
||||
for (VectorData vector : new HashSet<>(existingVelocities)) {
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.04f, 0), vector, VectorData.VectorType.Jump));
|
||||
|
||||
if (player.skippedTickInActualMovement) {
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.02f, 0)), vector, VectorData.VectorType.Jump));
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.02f, 0), vector, VectorData.VectorType.Jump));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
|
||||
super.endOfTick(player, playerGravity);
|
||||
|
||||
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
|
||||
vector.vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
|
||||
vector.vector.multiply(swimmingFriction, 0.8F, swimmingFriction);
|
||||
|
||||
// Gravity
|
||||
vector.vector.setY(vector.vector.getY() - 0.02D);
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public final class PredictionEngineRideableUtils {
|
||||
}
|
||||
|
||||
final double multiplier = (double) (22.2222F * player.vehicleData.horseJump) * camel.getAttributeValue(Attributes.MOVEMENT_SPEED) * (double) BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.lastX, player.lastY, player.lastZ));
|
||||
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(new Vector3dm(1.0, 0.0, 1.0)).normalize().multiply(multiplier).add(new Vector3dm(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0));
|
||||
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(1.0, 0.0, 1.0).normalize().multiply(multiplier).add(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0);
|
||||
|
||||
for (VectorData vectorData : possibleVectors) {
|
||||
vectorData.vector.add(jumpVelocity);
|
||||
|
||||
@@ -54,22 +54,85 @@ public class MessageUtil {
|
||||
return replacePlaceholders(player == null ? null : GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player.getUniqueId()), player, string, false);
|
||||
}
|
||||
|
||||
private static final Pattern UNIFIED_PLACEHOLDER_PATTERN = Pattern.compile("%([a-zA-Z0-9_]+)%");
|
||||
|
||||
@Contract("_, _, null, _ -> null; _, _, !null, _ -> !null")
|
||||
private @Nullable String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @Nullable String string, boolean removeFormatting) {
|
||||
if (string == null) return null;
|
||||
for (Map.Entry<String, String> entry : GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().entrySet()) {
|
||||
string = string.replace(entry.getKey(), entry.getValue());
|
||||
|
||||
// --- 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.
|
||||
if (string.indexOf('%') == -1) {
|
||||
// Since there are no % signs we can skip calling papi or our own replacement code
|
||||
return string;
|
||||
}
|
||||
|
||||
if (grimPlayer != null) {
|
||||
for (Map.Entry<String, Function<GrimUser, String>> entry : GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().entrySet()) {
|
||||
String value = entry.getValue().apply(grimPlayer).replace('%', PLACEHOLDER_ESCAPE_CHAR);
|
||||
if (removeFormatting) value = filterDiscordText(value);
|
||||
string = string.replace(entry.getKey(), value);
|
||||
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 (!matcher.find()) {
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// --- OPTIMIZATION 2: THE UNIFIED SINGLE-PASS LOOP ---
|
||||
// We use a do-while loop because we already performed the first matcher.find().
|
||||
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.
|
||||
String staticValue = staticReplacements.get(keyWithPercent);
|
||||
|
||||
if (staticValue != null) {
|
||||
value = staticValue;
|
||||
} else if (grimPlayer != null) {
|
||||
// If it's not a static placeholder, check if it's a dynamic one.
|
||||
// This is a second, O(1) hash map lookup.
|
||||
final Function<GrimUser, String> func = variableReplacements.get(keyWithPercent);
|
||||
if (func != null) {
|
||||
// LAZY EVALUATION: We only call the expensive function (like getTPS)
|
||||
// if we actually found its placeholder in the string.
|
||||
value = func.apply(grimPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
|
||||
// If we found no replacement, `value` will be null.
|
||||
// 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
|
||||
// check and the cost is negligible compared to the string operations.
|
||||
if (removeFormatting) {
|
||||
// Note: This assumes `filterDiscordText` is reasonably fast.
|
||||
// If it's slow, there are further micro-optimizations, but this is the right place for it.
|
||||
value = filterDiscordText(value);
|
||||
}
|
||||
|
||||
// `appendReplacement` efficiently appends the text between matches and our replacement value.
|
||||
// `Matcher.quoteReplacement` should handle any '$' or '\' in the replacement value.
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(value));
|
||||
|
||||
} while (matcher.find());
|
||||
|
||||
// Append the final part of the string after the last found placeholder.
|
||||
matcher.appendTail(sb);
|
||||
|
||||
// Create the final string from our builder.
|
||||
String grimReplaced = sb.toString();
|
||||
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, grimReplaced).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
|
||||
}
|
||||
|
||||
public static String filterDiscordText(String message) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import ac.grim.grimac.utils.collisions.blocks.DoorHandler;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.HitData;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.latency.CompensatedWorld;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
@@ -52,14 +52,14 @@ public class BlockPlace {
|
||||
private final GrimPlayer player;
|
||||
public final ItemStack itemStack;
|
||||
public final StateType material;
|
||||
public final @Nullable HitData hitData;
|
||||
public final @Nullable BlockHitData hitData;
|
||||
@Getter private int faceId;
|
||||
@Getter private BlockFace face;
|
||||
public boolean isInside;
|
||||
public Vector3f cursor;
|
||||
public final int sequence;
|
||||
|
||||
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i position, int faceId, BlockFace face, ItemStack itemStack, @Nullable HitData hitData, int sequence) {
|
||||
public BlockPlace(GrimPlayer player, InteractionHand hand, Vector3i position, int faceId, BlockFace face, ItemStack itemStack, @Nullable BlockHitData hitData, int sequence) {
|
||||
this.player = player;
|
||||
this.hand = hand;
|
||||
this.position = position;
|
||||
|
||||
@@ -22,7 +22,7 @@ import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntityStrider;
|
||||
import ac.grim.grimac.utils.nmsutil.Materials;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.item.ItemStack;
|
||||
@@ -1018,6 +1018,30 @@ public enum CollisionData implements CollisionFactory {
|
||||
}
|
||||
}, StateTypes.DRIED_GHAST),
|
||||
|
||||
SHELF((player, version, data, x, y, z) -> {
|
||||
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
|
||||
// ViaVersion replacement block (planks)
|
||||
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
|
||||
}
|
||||
|
||||
return switch (data.getFacing()) {
|
||||
case NORTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 11, 16, 16, 13), new HexCollisionBox(0, 0, 13, 16, 16, 16), new HexCollisionBox(0, 0, 11, 16, 4, 13));
|
||||
case SOUTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 3, 16, 16, 5), new HexCollisionBox(0, 0, 0, 16, 16, 3), new HexCollisionBox(0, 0, 3, 16, 4, 5));
|
||||
case WEST -> new ComplexCollisionBox(3, new HexCollisionBox(11, 12, 0, 13, 16, 16), new HexCollisionBox(13, 0, 0, 16, 16, 16), new HexCollisionBox(11, 0, 0, 13, 4, 16));
|
||||
case EAST -> new ComplexCollisionBox(3, new HexCollisionBox(3, 12, 0, 5, 16, 16), new HexCollisionBox(0, 0, 0, 3, 16, 16), new HexCollisionBox(3, 0, 0, 5, 4, 16));
|
||||
default -> throw new IllegalStateException("Unexpected value: " + data.getFacing());
|
||||
};
|
||||
}, BlockTags.WOODEN_SHELVES.getStates().toArray(new StateType[0])),
|
||||
|
||||
COPPER_GOLEM_STATUE((player, version, data, x, y, z) -> {
|
||||
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
|
||||
// ViaVersion replacement block (copper block)
|
||||
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
|
||||
}
|
||||
|
||||
return new HexCollisionBox(3, 0, 3, 13, 14, 13);
|
||||
}, BlockTags.COPPER_GOLEM_STATUES.getStates().toArray(new StateType[0])),
|
||||
|
||||
DEFAULT(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true), StateTypes.STONE);
|
||||
|
||||
// This should be an array... but a hashmap will do for now...
|
||||
@@ -1049,7 +1073,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
this.materials = mList.toArray(new StateType[0]);
|
||||
}
|
||||
|
||||
private static CollisionBox getAmethystBox(ClientVersion version, com.github.retrooper.packetevents.protocol.world.BlockFace facing, int param_0, int param_1) {
|
||||
private static CollisionBox getAmethystBox(ClientVersion version, BlockFace facing, int param_0, int param_1) {
|
||||
if (version.isOlderThanOrEquals(ClientVersion.V_1_16_4))
|
||||
return NoCollisionBox.INSTANCE;
|
||||
|
||||
@@ -1146,7 +1170,11 @@ public enum CollisionData implements CollisionFactory {
|
||||
// Would pre-computing all states be worth the memory cost? I doubt it
|
||||
public static CollisionData getData(StateType state) { // TODO: Find a better hack for lava and scaffolding
|
||||
// What the fuck mojang, why put noCollision() and then give PITCHER_CROP collision?
|
||||
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING || state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE || state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state) ? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
|
||||
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING
|
||||
|| state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE
|
||||
|| state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state)
|
||||
|| BlockTags.COPPER_GOLEM_STATUES.contains(state)
|
||||
? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
|
||||
}
|
||||
|
||||
// TODO: This is wrong if a block doesn't have any hitbox and isn't specified, light block?
|
||||
|
||||
@@ -17,4 +17,16 @@ public class CommonGrimArguments {
|
||||
public final static SystemArgument<String> PASTE_URL = FACTORY.create(string("PasteUrl", "https://paste.grim.ac/"));
|
||||
public final static SystemArgument<Platform> PLATFORM_OVERRIDE = FACTORY.create(platform("PlatformOverride"));
|
||||
|
||||
/**
|
||||
* Enables "Fast Bypass" mode for chat messages sent by GrimAC.
|
||||
* <p>
|
||||
* <b>BENEFIT:</b> Messages are sent directly as packets, significantly improving
|
||||
* performance and reducing server overhead especially when lots of alerts are being sent.
|
||||
* <p>
|
||||
* <b>TRADE-OFF:</b> This completely bypasses the platform's event system (e.g., Bukkit's chat events).
|
||||
* Other plugins will NOT be able to see, format, or cancel these messages.
|
||||
* <p>
|
||||
* This setting is opt-in (default: false) and requires a server restart to change.
|
||||
*/
|
||||
public final static SystemArgument<Boolean> USE_CHAT_FAST_BYPASS = FACTORY.create(string("ChatFastBypass", true));
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package ac.grim.grimac.utils.data;
|
||||
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Accessors(fluent = true)
|
||||
@ToString
|
||||
public class BlockHitData extends HitData {
|
||||
Vector3i position;
|
||||
WrappedBlockState state;
|
||||
BlockFace closestDirection;
|
||||
// public Boolean success;
|
||||
|
||||
public BlockHitData(Vector3i position, Vector3dm blockHitLocation, BlockFace closestDirection, WrappedBlockState state
|
||||
// , Boolean success
|
||||
) {
|
||||
super(blockHitLocation);
|
||||
this.position = position;
|
||||
this.closestDirection = closestDirection;
|
||||
this.state = state;
|
||||
// this.success = success;
|
||||
}
|
||||
|
||||
public Vector3d getRelativeBlockHitLocation() {
|
||||
return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ac.grim.grimac.utils.data;
|
||||
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class EntityHitData extends HitData {
|
||||
private final PacketEntity entity;
|
||||
|
||||
public EntityHitData(PacketEntity packetEntity) {
|
||||
this(packetEntity, new Vector3dm(packetEntity.trackedServerPosition.getPos().x,
|
||||
packetEntity.trackedServerPosition.getPos().y,
|
||||
packetEntity.trackedServerPosition.getPos().z));
|
||||
}
|
||||
|
||||
public EntityHitData(PacketEntity packetEntity, Vector3dm intersectionPoint) {
|
||||
super(intersectionPoint); // Use actual intersection point
|
||||
this.entity = packetEntity;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
package ac.grim.grimac.utils.data;
|
||||
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
|
||||
public record HitData(
|
||||
Vector3i position,
|
||||
Vector3dm blockHitLocation,
|
||||
BlockFace closestDirection,
|
||||
WrappedBlockState state
|
||||
) {
|
||||
public Vector3d getRelativeBlockHitLocation() {
|
||||
return new Vector3d(blockHitLocation.getX() - position.getX(), blockHitLocation.getY() - position.getY(), blockHitLocation.getZ() - position.getZ());
|
||||
public class HitData {
|
||||
Vector3dm blockHitLocation;
|
||||
|
||||
public HitData(Vector3dm blockHitLocation) {
|
||||
this.blockHitLocation = blockHitLocation;
|
||||
}
|
||||
|
||||
public ac.grim.grimac.utils.math.Vector3dm getBlockHitLocation() {
|
||||
return this.blockHitLocation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ac.grim.grimac.utils.data;
|
||||
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PistonTemplate(BlockFace dir,
|
||||
List<SimpleCollisionBox> boxes,
|
||||
boolean push, boolean slime, boolean honey) {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ac.grim.grimac.utils.data;
|
||||
|
||||
|
||||
|
||||
public class PlayerPistonData {
|
||||
public final PistonTemplate pistonTemplate;
|
||||
public final int lastTransactionSent;
|
||||
|
||||
// Calculate if the player has no-push, and when to end the possibility of applying piston
|
||||
int ticksOfPistonBeingAlive = 0;
|
||||
|
||||
public PlayerPistonData(PistonTemplate playerPistonData, int lastTransactionSent) {
|
||||
this.pistonTemplate = playerPistonData;
|
||||
this.lastTransactionSent = lastTransactionSent;
|
||||
}
|
||||
|
||||
// We don't know when the piston has applied, or what stage of pushing it is on
|
||||
// Therefore, we need to use what we have - the number of movement packets.
|
||||
// 10 is a very cautious number
|
||||
public boolean tickIfGuaranteedFinished() {
|
||||
return ++ticksOfPistonBeingAlive >= 10;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class ReachInterpolationData {
|
||||
private int cancelledLerpInterpolationStepsLowBound = Integer.MAX_VALUE;
|
||||
|
||||
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, TrackedPosition position, PacketEntity entity) {
|
||||
final boolean isPointNine = !player.inVehicle() && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9);
|
||||
final boolean unreliableTicking = !player.inVehicle() && player.canSkipTicks();
|
||||
|
||||
this.startingLocation = startingLocation;
|
||||
final Vector3d pos = position.getPos();
|
||||
@@ -50,7 +50,7 @@ public class ReachInterpolationData {
|
||||
|
||||
// 1.9 -> 1.8 precision loss in packets
|
||||
// (ViaVersion is doing some stuff that makes this code difficult)
|
||||
if (!isPointNine && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
|
||||
targetLocation.expand(0.03125);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ public class ReachInterpolationData {
|
||||
interpolationSteps = 1;
|
||||
}
|
||||
|
||||
if (isPointNine) interpolationStepsHighBound = getInterpolationSteps();
|
||||
// If the player doesn't tick reliably, their interpolation is anywhere between min and max steps.
|
||||
if (unreliableTicking) interpolationStepsHighBound = getInterpolationSteps();
|
||||
}
|
||||
|
||||
// While riding entities, there is no interpolation.
|
||||
@@ -200,6 +201,133 @@ public class ReachInterpolationData {
|
||||
return minimumInterpLocation;
|
||||
}
|
||||
|
||||
public CollisionBox getOverlapHitboxCombined() {
|
||||
int interpSteps = getInterpolationSteps();
|
||||
|
||||
// Calculate step increments for each axis
|
||||
double stepMinX = (targetLocation.minX - startingLocation.minX) / (double) interpSteps;
|
||||
double stepMaxX = (targetLocation.maxX - startingLocation.maxX) / (double) interpSteps;
|
||||
double stepMinY = (targetLocation.minY - startingLocation.minY) / (double) interpSteps;
|
||||
double stepMaxY = (targetLocation.maxY - startingLocation.maxY) / (double) interpSteps;
|
||||
double stepMinZ = (targetLocation.minZ - startingLocation.minZ) / (double) interpSteps;
|
||||
double stepMaxZ = (targetLocation.maxZ - startingLocation.maxZ) / (double) interpSteps;
|
||||
|
||||
// Track the intersection of all expanded hitboxes
|
||||
double overallMinX = Double.NEGATIVE_INFINITY;
|
||||
double overallMaxX = Double.POSITIVE_INFINITY;
|
||||
double overallMinY = Double.NEGATIVE_INFINITY;
|
||||
double overallMaxY = Double.POSITIVE_INFINITY;
|
||||
double overallMinZ = Double.NEGATIVE_INFINITY;
|
||||
double overallMaxZ = Double.POSITIVE_INFINITY;
|
||||
|
||||
boolean isFirstStep = true;
|
||||
|
||||
for (int step = interpolationStepsLowBound; step <= interpolationStepsHighBound; step++) {
|
||||
// Compute interpolated position for this step
|
||||
double currentMinX = startingLocation.minX + (step * stepMinX);
|
||||
double currentMaxX = startingLocation.maxX + (step * stepMaxX);
|
||||
double currentMinY = startingLocation.minY + (step * stepMinY);
|
||||
double currentMaxY = startingLocation.maxY + (step * stepMaxY);
|
||||
double currentMinZ = startingLocation.minZ + (step * stepMinZ);
|
||||
double currentMaxZ = startingLocation.maxZ + (step * stepMaxZ);
|
||||
|
||||
// Create the collision box for this step's position
|
||||
// Create boxes for each bottom corner
|
||||
SimpleCollisionBox[] cornerBoxes = new SimpleCollisionBox[4];
|
||||
|
||||
// Bottom corners: (minX,minY,minZ), (maxX,minY,minZ), (minX,minY,maxZ), (maxX,minY,maxZ)
|
||||
cornerBoxes[0] = new SimpleCollisionBox(currentMinX, currentMinY, currentMinZ,
|
||||
currentMinX, currentMinY, currentMinZ);
|
||||
cornerBoxes[1] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMinZ,
|
||||
currentMaxX, currentMinY, currentMinZ);
|
||||
cornerBoxes[2] = new SimpleCollisionBox(currentMinX, currentMinY, currentMaxZ,
|
||||
currentMinX, currentMinY, currentMaxZ);
|
||||
cornerBoxes[3] = new SimpleCollisionBox(currentMaxX, currentMinY, currentMaxZ,
|
||||
currentMaxX, currentMinY, currentMaxZ);
|
||||
|
||||
// Expand each corner box by entity dimensions
|
||||
for (SimpleCollisionBox cornerBox : cornerBoxes) {
|
||||
GetBoundingBox.expandBoundingBoxByEntityDimensions(cornerBox, player, entity);
|
||||
}
|
||||
|
||||
// Get the overlap of the 4 corner boxes
|
||||
CollisionBox stepOverlap = getOverlapOfBoxes(cornerBoxes);
|
||||
if (stepOverlap == NoCollisionBox.INSTANCE)
|
||||
return NoCollisionBox.INSTANCE;
|
||||
SimpleCollisionBox stepBox = (SimpleCollisionBox) stepOverlap;
|
||||
|
||||
// Initialize overall bounds with the first expanded box
|
||||
if (isFirstStep) {
|
||||
overallMinX = stepBox.minX;
|
||||
overallMaxX = stepBox.maxX;
|
||||
overallMinY = stepBox.minY;
|
||||
overallMaxY = stepBox.maxY;
|
||||
overallMinZ = stepBox.minZ;
|
||||
overallMaxZ = stepBox.maxZ;
|
||||
isFirstStep = false;
|
||||
} else {
|
||||
// Update bounds to the intersection of all expanded boxes
|
||||
overallMinX = Math.max(overallMinX, stepBox.minX);
|
||||
overallMaxX = Math.min(overallMaxX, stepBox.maxX);
|
||||
overallMinY = Math.max(overallMinY, stepBox.minY);
|
||||
overallMaxY = Math.min(overallMaxY, stepBox.maxY);
|
||||
overallMinZ = Math.max(overallMinZ, stepBox.minZ);
|
||||
overallMaxZ = Math.min(overallMaxZ, stepBox.maxZ);
|
||||
}
|
||||
|
||||
// Early exit if the intersection becomes empty
|
||||
if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) {
|
||||
return NoCollisionBox.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the final intersection is valid
|
||||
if (overallMinX > overallMaxX || overallMinY > overallMaxY || overallMinZ > overallMaxZ) {
|
||||
return NoCollisionBox.INSTANCE;
|
||||
}
|
||||
|
||||
return new SimpleCollisionBox(
|
||||
overallMinX, overallMinY, overallMinZ,
|
||||
overallMaxX, overallMaxY, overallMaxZ
|
||||
);
|
||||
}
|
||||
|
||||
private CollisionBox getOverlapOfBoxes(SimpleCollisionBox[] boxes) {
|
||||
double minX = Double.NEGATIVE_INFINITY;
|
||||
double maxX = Double.POSITIVE_INFINITY;
|
||||
double minY = Double.NEGATIVE_INFINITY;
|
||||
double maxY = Double.POSITIVE_INFINITY;
|
||||
double minZ = Double.NEGATIVE_INFINITY;
|
||||
double maxZ = Double.POSITIVE_INFINITY;
|
||||
|
||||
boolean first = true;
|
||||
|
||||
for (SimpleCollisionBox box : boxes) {
|
||||
if (first) {
|
||||
minX = box.minX;
|
||||
maxX = box.maxX;
|
||||
minY = box.minY;
|
||||
maxY = box.maxY;
|
||||
minZ = box.minZ;
|
||||
maxZ = box.maxZ;
|
||||
first = false;
|
||||
} else {
|
||||
minX = Math.max(minX, box.minX);
|
||||
maxX = Math.min(maxX, box.maxX);
|
||||
minY = Math.max(minY, box.minY);
|
||||
maxY = Math.min(maxY, box.maxY);
|
||||
minZ = Math.max(minZ, box.minZ);
|
||||
maxZ = Math.min(maxZ, box.maxZ);
|
||||
}
|
||||
|
||||
if (minX > maxX || minY > maxY || minZ > maxZ) {
|
||||
return NoCollisionBox.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public void updatePossibleStartingLocation(SimpleCollisionBox possibleLocationCombined) {
|
||||
//GrimACBukkitLoaderPlugin.staticGetLogger().info(ChatColor.BLUE + "Updated new starting location as second trans hasn't arrived " + startingLocation);
|
||||
this.startingLocation = combineCollisionBox(startingLocation, possibleLocationCombined);
|
||||
|
||||
@@ -7,6 +7,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import java.util.Objects;
|
||||
|
||||
public class VectorData {
|
||||
public static final class MoveVectorData extends VectorData {
|
||||
public int x;
|
||||
public int z;
|
||||
|
||||
public MoveVectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType, int x, int z) {
|
||||
super(vector, lastVector, vectorType);
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
|
||||
if (x != 0 || z != 0) {
|
||||
addVectorType(VectorType.WithInput);
|
||||
}
|
||||
}
|
||||
|
||||
public MoveVectorData(Vector3dm vector, VectorType vectorType, int x, int z) {
|
||||
super(vector, vectorType);
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
|
||||
if (x != 0 || z != 0) {
|
||||
addVectorType(VectorType.WithInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final VectorType vectorType;
|
||||
public VectorData lastVector;
|
||||
public VectorData preUncertainty;
|
||||
@@ -14,7 +39,7 @@ public class VectorData {
|
||||
public @MonotonicNonNull Vector3dm input;
|
||||
|
||||
@Getter
|
||||
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false;
|
||||
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false, isWithInput = false;
|
||||
|
||||
// For handling replacing the type of vector it is while keeping data
|
||||
public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) {
|
||||
@@ -36,6 +61,7 @@ public class VectorData {
|
||||
preUncertainty = lastVector.preUncertainty;
|
||||
isAttackSlow = lastVector.isAttackSlow;
|
||||
input = lastVector.input;
|
||||
isWithInput = lastVector.isWithInput;
|
||||
}
|
||||
|
||||
addVectorType(vectorType);
|
||||
@@ -81,6 +107,7 @@ public class VectorData {
|
||||
case Flip_Use_Item -> isFlipItem = true;
|
||||
case Jump -> isJump = true;
|
||||
case AttackSlow -> isAttackSlow = true;
|
||||
case WithInput -> isWithInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +134,7 @@ public class VectorData {
|
||||
Explosion,
|
||||
FirstBreadExplosion,
|
||||
InputResult,
|
||||
WithInput,
|
||||
StuckMultiplier,
|
||||
Spectator,
|
||||
Dead,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package ac.grim.grimac.utils.data.packetentity;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.ReachInterpolationData;
|
||||
import ac.grim.grimac.utils.data.TrackedPosition;
|
||||
@@ -229,6 +230,14 @@ public class PacketEntity extends TypedPacketEntity {
|
||||
return ReachInterpolationData.combineCollisionBox(oldPacketLocation.getPossibleHitboxCombined(), newPacketLocation.getPossibleHitboxCombined());
|
||||
}
|
||||
|
||||
public CollisionBox getMinimumPossibleCollisionBoxes() {
|
||||
if (oldPacketLocation == null) {
|
||||
return newPacketLocation.getOverlapHitboxCombined();
|
||||
}
|
||||
|
||||
return ReachInterpolationData.getOverlapHitbox(oldPacketLocation.getOverlapHitboxCombined(), newPacketLocation.getOverlapHitboxCombined());
|
||||
}
|
||||
|
||||
public OptionalInt getPotionEffectLevel(PotionType effect) {
|
||||
final int amplifier = potionsMap == null ? -1 : potionsMap.getInt(effect);
|
||||
return amplifier == -1 ? OptionalInt.empty() : OptionalInt.of(amplifier);
|
||||
|
||||
@@ -16,4 +16,11 @@ public class PacketEntityPainting extends PacketEntity {
|
||||
super(player, uuid, EntityTypes.PAINTING, x, y, z);
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
// This is incorrect, temporary measure to exempt paintings from HitboxEntity
|
||||
// Will properly model later
|
||||
@Override
|
||||
public boolean canHit() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -33,23 +33,26 @@ public enum EquipmentType {
|
||||
}
|
||||
if (item == ItemTypes.LEATHER_BOOTS || item == ItemTypes.CHAINMAIL_BOOTS
|
||||
|| item == ItemTypes.IRON_BOOTS || item == ItemTypes.DIAMOND_BOOTS
|
||||
|| item == ItemTypes.GOLDEN_BOOTS || item == ItemTypes.NETHERITE_BOOTS) {
|
||||
|| item == ItemTypes.GOLDEN_BOOTS || item == ItemTypes.NETHERITE_BOOTS
|
||||
|| item == ItemTypes.COPPER_BOOTS) {
|
||||
return FEET;
|
||||
}
|
||||
if (item == ItemTypes.LEATHER_LEGGINGS || item == ItemTypes.CHAINMAIL_LEGGINGS
|
||||
|| item == ItemTypes.IRON_LEGGINGS || item == ItemTypes.DIAMOND_LEGGINGS
|
||||
|| item == ItemTypes.GOLDEN_LEGGINGS || item == ItemTypes.NETHERITE_LEGGINGS) {
|
||||
|| item == ItemTypes.GOLDEN_LEGGINGS || item == ItemTypes.NETHERITE_LEGGINGS
|
||||
|| item == ItemTypes.COPPER_LEGGINGS) {
|
||||
return LEGS;
|
||||
}
|
||||
if (item == ItemTypes.LEATHER_CHESTPLATE || item == ItemTypes.CHAINMAIL_CHESTPLATE
|
||||
|| item == ItemTypes.IRON_CHESTPLATE || item == ItemTypes.DIAMOND_CHESTPLATE
|
||||
|| item == ItemTypes.GOLDEN_CHESTPLATE || item == ItemTypes.NETHERITE_CHESTPLATE) {
|
||||
|| item == ItemTypes.GOLDEN_CHESTPLATE || item == ItemTypes.NETHERITE_CHESTPLATE
|
||||
|| item == ItemTypes.COPPER_CHESTPLATE) {
|
||||
return CHEST;
|
||||
}
|
||||
if (item == ItemTypes.LEATHER_HELMET || item == ItemTypes.CHAINMAIL_HELMET
|
||||
|| item == ItemTypes.IRON_HELMET || item == ItemTypes.DIAMOND_HELMET
|
||||
|| item == ItemTypes.GOLDEN_HELMET || item == ItemTypes.NETHERITE_HELMET
|
||||
|| item == ItemTypes.TURTLE_HELMET) {
|
||||
|| item == ItemTypes.COPPER_HELMET || item == ItemTypes.TURTLE_HELMET) {
|
||||
return HEAD;
|
||||
}
|
||||
return ItemTypes.SHIELD == item ? OFFHAND : MAINHAND;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package ac.grim.grimac.utils.inventory;
|
||||
|
||||
public enum InventoryDesyncStatus {
|
||||
BEACON,
|
||||
NETHER_PORTAL,
|
||||
NOT_DESYNCED
|
||||
}
|
||||
@@ -287,9 +287,9 @@ public class CompensatedEntities {
|
||||
if (sizeObject != null) {
|
||||
Object value = sizeObject.getValue();
|
||||
if (value instanceof Integer) {
|
||||
sizeable.size = Math.max((int) value, 1);
|
||||
sizeable.size = (int) value;
|
||||
} else if (value instanceof Byte) {
|
||||
sizeable.size = Math.max((byte) value, 1);
|
||||
sizeable.size = (byte) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
|
||||
public boolean isPacketInventoryActive = true;
|
||||
public boolean needResend = false;
|
||||
public int stateID = 0; // Don't mess up the last sent state ID by changing it
|
||||
private int openWindowID = 0;
|
||||
public int openWindowID = 0;
|
||||
// Special values:
|
||||
// Player inventory is -1
|
||||
// Unsupported inventory is -2
|
||||
|
||||
@@ -6,10 +6,7 @@ import ac.grim.grimac.utils.change.BlockModification;
|
||||
import ac.grim.grimac.utils.chunks.Column;
|
||||
import ac.grim.grimac.utils.collisions.CollisionData;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.BlockPrediction;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.data.PistonData;
|
||||
import ac.grim.grimac.utils.data.ShulkerData;
|
||||
import ac.grim.grimac.utils.data.*;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntityShulker;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
@@ -69,7 +66,7 @@ public class CompensatedWorld {
|
||||
public final GrimPlayer player;
|
||||
public final Long2ObjectMap<Column> chunks;
|
||||
// Packet locations for blocks
|
||||
public final Set<PistonData> activePistons = new HashSet<>();
|
||||
public final Set<PlayerPistonData> activePistons = new HashSet<>();
|
||||
public final Set<ShulkerData> openShulkerBoxes = new HashSet<>();
|
||||
// 1.17 with datapacks, and 1.18, have negative world offset values
|
||||
@Getter
|
||||
@@ -238,8 +235,8 @@ public class CompensatedWorld {
|
||||
}
|
||||
|
||||
// Pistons are a block entity.
|
||||
for (PistonData data : activePistons) {
|
||||
for (SimpleCollisionBox box : data.boxes) {
|
||||
for (PlayerPistonData data : activePistons) {
|
||||
for (SimpleCollisionBox box : data.pistonTemplate.boxes()) {
|
||||
if (playerBox.isCollided(box)) {
|
||||
return true;
|
||||
}
|
||||
@@ -358,18 +355,18 @@ public class CompensatedWorld {
|
||||
double modY = 0;
|
||||
double modZ = 0;
|
||||
|
||||
for (PistonData data : activePistons) {
|
||||
for (SimpleCollisionBox box : data.boxes) {
|
||||
for (PlayerPistonData data : activePistons) {
|
||||
for (SimpleCollisionBox box : data.pistonTemplate.boxes()) {
|
||||
if (playerBox.isCollided(box)) {
|
||||
modX = Math.max(modX, Math.abs(data.direction.getModX() * 0.51D));
|
||||
modY = Math.max(modY, Math.abs(data.direction.getModY() * 0.51D));
|
||||
modZ = Math.max(modZ, Math.abs(data.direction.getModZ() * 0.51D));
|
||||
modX = Math.max(modX, Math.abs(data.pistonTemplate.dir().getModX() * 0.51D));
|
||||
modY = Math.max(modY, Math.abs(data.pistonTemplate.dir().getModY() * 0.51D));
|
||||
modZ = Math.max(modZ, Math.abs(data.pistonTemplate.dir().getModZ() * 0.51D));
|
||||
|
||||
playerBox.expandMax(modX, modY, modZ);
|
||||
playerBox.expandMin(modX * -1, modY * -1, modZ * -1);
|
||||
|
||||
if (data.hasSlimeBlock || (data.hasHoneyBlock && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) {
|
||||
player.uncertaintyHandler.slimePistonBounces.add(data.direction);
|
||||
if (data.pistonTemplate.slime() || (data.pistonTemplate.honey() && player.getClientVersion().isOlderThan(ClientVersion.V_1_15_2))) {
|
||||
player.uncertaintyHandler.slimePistonBounces.add(data.pistonTemplate.dir());
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -424,7 +421,7 @@ public class CompensatedWorld {
|
||||
activePistons.removeIf(data -> data.lastTransactionSent < transactionId);
|
||||
openShulkerBoxes.removeIf(data -> data.isClosing && data.lastTransactionSent < transactionId);
|
||||
} else {
|
||||
activePistons.removeIf(PistonData::tickIfGuaranteedFinished);
|
||||
activePistons.removeIf(PlayerPistonData::tickIfGuaranteedFinished);
|
||||
openShulkerBoxes.removeIf(ShulkerData::tickIfGuaranteedFinished);
|
||||
}
|
||||
// Remove if a shulker is not in this block position anymore
|
||||
@@ -697,4 +694,8 @@ public class CompensatedWorld {
|
||||
public WrappedBlockState getBlock(Vector3dm aboveCCWPos) {
|
||||
return getBlock(aboveCCWPos.getX(), aboveCCWPos.getY(), aboveCCWPos.getZ());
|
||||
}
|
||||
|
||||
public void addPiston(PistonTemplate pistonTemplate, int transactionID) {
|
||||
activePistons.add(new PlayerPistonData(pistonTemplate, transactionID));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ac.grim.grimac.utils.latency;
|
||||
|
||||
public interface ILatencyUtils {
|
||||
/**
|
||||
* Adds a task to be executed when the corresponding transaction ACK is received.
|
||||
*
|
||||
* @param transaction The transaction ID this task is associated with.
|
||||
* @param runnable The task to execute.
|
||||
*/
|
||||
void addRealTimeTask(int transaction, Runnable runnable);
|
||||
|
||||
/**
|
||||
* Adds a task to be executed asynchronously via the player's event loop
|
||||
* when the corresponding transaction ACK is received.
|
||||
* (Note: Benchmark might simplify/ignore the async part unless specifically testing event loop contention)
|
||||
*
|
||||
* @param transaction The transaction ID this task is associated with.
|
||||
* @param runnable The task to execute.
|
||||
*/
|
||||
void addRealTimeTaskAsync(int transaction, Runnable runnable);
|
||||
|
||||
/**
|
||||
* Processes received transaction ACKs and runs associated tasks.
|
||||
*
|
||||
* @param receivedTransactionId The ID of the transaction ACK received from the client.
|
||||
*/
|
||||
void handleNettySyncTransaction(int receivedTransactionId);
|
||||
}
|
||||
@@ -5,18 +5,17 @@ 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.common.arguments.CommonGrimArguments;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ListIterator;
|
||||
import java.util.*;
|
||||
|
||||
public class LatencyUtils implements ILatencyUtils {
|
||||
|
||||
// Record to replace Pair with primitive int
|
||||
private record TransactionTask(int transactionId, Runnable task) {}
|
||||
|
||||
private final ArrayDeque<TransactionTask> transactionMap = new ArrayDeque<>();
|
||||
|
||||
public class LatencyUtils {
|
||||
private final LinkedList<Pair<Integer, Runnable>> transactionMap = new LinkedList<>();
|
||||
private final GrimPlayer player;
|
||||
|
||||
// Built from transactionMap and cleared at start of every handleNettySyncTransaction() call
|
||||
// The actual usage scope of this variable's use is limited to within the synchronized block of handleNettySyncTransaction
|
||||
private final ArrayList<Runnable> tasksToRun = new ArrayList<>();
|
||||
|
||||
public LatencyUtils(GrimPlayer player) {
|
||||
@@ -24,15 +23,15 @@ public class LatencyUtils {
|
||||
}
|
||||
|
||||
public void addRealTimeTask(int transaction, Runnable runnable) {
|
||||
addRealTimeTask(transaction, false, runnable);
|
||||
addRealTimeTaskInternal(transaction, false, runnable);
|
||||
}
|
||||
|
||||
public void addRealTimeTaskAsync(int transaction, Runnable runnable) {
|
||||
addRealTimeTask(transaction, true, runnable);
|
||||
addRealTimeTaskInternal(transaction, true, runnable);
|
||||
}
|
||||
|
||||
public void addRealTimeTask(int transaction, boolean async, Runnable runnable) {
|
||||
if (player.lastTransactionReceived.get() >= transaction) { // If the player already responded to this transaction
|
||||
private void addRealTimeTaskInternal(int transactionId, boolean async, Runnable runnable) {
|
||||
if (player.lastTransactionReceived.get() >= transactionId) {
|
||||
if (async) {
|
||||
player.runSafely(runnable);
|
||||
} else {
|
||||
@@ -40,62 +39,54 @@ public class LatencyUtils {
|
||||
}
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
transactionMap.add(new Pair<>(transaction, runnable));
|
||||
synchronized (transactionMap) {
|
||||
transactionMap.add(new TransactionTask(transactionId, runnable));
|
||||
}
|
||||
}
|
||||
|
||||
public void handleNettySyncTransaction(int transaction) {
|
||||
/*
|
||||
* This code uses a two-pass approach within the synchronized block to prevent CMEs.
|
||||
* First we collect and remove tasks using the iterator, then execute all collected tasks.
|
||||
*
|
||||
* The issue:
|
||||
* We cannot execute tasks during iteration because if a runnable modifies transactionMap
|
||||
* or calls addRealTimeTask, it will cause a ConcurrentModificationException.
|
||||
* While only seen on Folia servers, this is theoretically possible everywhere.
|
||||
*
|
||||
* Why this solution:
|
||||
* Rather than documenting "don't modify transactionMap in runnables" and risking subtle
|
||||
* bugs from future contributions or Check API usage, we prevent the issue entirely
|
||||
* at a small performance cost.
|
||||
*
|
||||
* Future considerations:
|
||||
* If this becomes a performance bottleneck, we may revisit using a single-pass approach
|
||||
* on non-Folia servers. We could also explore concurrent data structures or parallel
|
||||
* execution, but this would lose the guarantee that transactions are processed in order.
|
||||
*/
|
||||
synchronized (this) {
|
||||
@Override
|
||||
public void handleNettySyncTransaction(int receivedTransactionId) {
|
||||
synchronized (transactionMap) {
|
||||
tasksToRun.clear();
|
||||
|
||||
// First pass: collect tasks and mark them for removal
|
||||
ListIterator<Pair<Integer, Runnable>> iterator = transactionMap.listIterator();
|
||||
Iterator<TransactionTask> iterator = transactionMap.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Pair<Integer, Runnable> pair = iterator.next();
|
||||
TransactionTask taskEntry = iterator.next();
|
||||
int taskTransactionId = taskEntry.transactionId();
|
||||
|
||||
// We are at most a tick ahead when running tasks based on transactions, meaning this is too far
|
||||
if (transaction + 1 < pair.first())
|
||||
// If tasks are added with monotonically increasing IDs,
|
||||
// once we find one that's too far ahead, all subsequent ones
|
||||
// will also be too far ahead.
|
||||
if (receivedTransactionId + 1 < taskTransactionId) {
|
||||
break;
|
||||
}
|
||||
|
||||
// This is at most tick ahead of what we want
|
||||
if (transaction == pair.first() - 1)
|
||||
continue;
|
||||
if (receivedTransactionId == taskTransactionId - 1) {
|
||||
continue; // Skip this specific task
|
||||
}
|
||||
|
||||
tasksToRun.add(pair.second());
|
||||
iterator.remove();
|
||||
// If we didn't break or continue, the task is eligible
|
||||
tasksToRun.add(taskEntry.task());
|
||||
iterator.remove(); // Remove using the iterator
|
||||
}
|
||||
|
||||
// Task execution loop
|
||||
for (Runnable runnable : tasksToRun) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e);
|
||||
// Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN
|
||||
if (CommonGrimArguments.KICK_ON_TRANSACTION_ERRORS.value()) {
|
||||
player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError())));
|
||||
}
|
||||
handleRunnableError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRunnableError(Exception e) {
|
||||
LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e);
|
||||
// Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN
|
||||
if (CommonGrimArguments.KICK_ON_TRANSACTION_ERRORS.value()) {
|
||||
player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,23 +66,35 @@ public class Vector3dm implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm add(@NotNull Vector3dm vec) {
|
||||
this.x += vec.x;
|
||||
this.y += vec.y;
|
||||
this.z += vec.z;
|
||||
return add(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm add(double x, double y, double z) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm subtract(@NotNull Vector3dm vec) {
|
||||
this.x -= vec.x;
|
||||
this.y -= vec.y;
|
||||
this.z -= vec.z;
|
||||
return subtract(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm subtract(double x, double y, double z) {
|
||||
this.x -= x;
|
||||
this.y -= y;
|
||||
this.z -= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm multiply(@NotNull Vector3dm vec) {
|
||||
this.x *= vec.x;
|
||||
this.y *= vec.y;
|
||||
this.z *= vec.z;
|
||||
return multiply(vec.x, vec.y, vec.z);
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm multiply(double x, double y, double z) {
|
||||
this.x *= x;
|
||||
this.y *= y;
|
||||
this.z *= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -109,11 +121,19 @@ public class Vector3dm implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
public double distance(@NotNull Vector3dm o) {
|
||||
return Math.sqrt(GrimMath.square(this.x - o.x) + GrimMath.square(this.y - o.y) + GrimMath.square(this.z - o.z));
|
||||
return Math.sqrt(distanceSquared(o));
|
||||
}
|
||||
|
||||
public double distanceSquared(@NotNull Vector3dm o) {
|
||||
return GrimMath.square(this.x - o.x) + GrimMath.square(this.y - o.y) + GrimMath.square(this.z - o.z);
|
||||
return distanceSquared(o.x, o.y, o.z);
|
||||
}
|
||||
|
||||
public double distance(double oX, double oY, double oZ) {
|
||||
return Math.sqrt(distanceSquared(oX, oY, oZ));
|
||||
}
|
||||
|
||||
public double distanceSquared(double oX, double oY, double oZ) {
|
||||
return GrimMath.square(this.x - oX) + GrimMath.square(this.y - oY) + GrimMath.square(this.z - oZ);
|
||||
}
|
||||
|
||||
public @NotNull Vector3dm midpoint(@NotNull Vector3dm other) {
|
||||
|
||||
@@ -15,9 +15,13 @@ public class VectorUtils {
|
||||
|
||||
@Contract("_, _ -> new")
|
||||
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorCutTo, @NotNull SimpleCollisionBox box) {
|
||||
return new Vector3dm(GrimMath.clamp(vectorCutTo.getX(), box.minX, box.maxX),
|
||||
GrimMath.clamp(vectorCutTo.getY(), box.minY, box.maxY),
|
||||
GrimMath.clamp(vectorCutTo.getZ(), box.minZ, box.maxZ));
|
||||
return cutBoxToVector(vectorCutTo.getX(), vectorCutTo.getY(), vectorCutTo.getZ(), box);
|
||||
}
|
||||
|
||||
public static @NotNull Vector3dm cutBoxToVector(double x, double y, double z, @NotNull SimpleCollisionBox box) {
|
||||
return new Vector3dm(GrimMath.clamp(x, box.minX, box.maxX),
|
||||
GrimMath.clamp(y, box.minY, box.maxY),
|
||||
GrimMath.clamp(z, box.minZ, box.maxZ));
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
|
||||
@@ -31,7 +31,7 @@ public final class BoundingBoxSize {
|
||||
return 0.75f;
|
||||
} else if (EntityTypes.PANDA.equals(type)) {
|
||||
return 1.3f;
|
||||
} else if (EntityTypes.BAT.equals(type) || EntityTypes.PARROT.equals(type) || EntityTypes.COD.equals(type) || EntityTypes.EVOKER_FANGS.equals(type) || EntityTypes.TROPICAL_FISH.equals(type) || EntityTypes.FROG.equals(type)) {
|
||||
} else if (EntityTypes.BAT.equals(type) || EntityTypes.PARROT.equals(type) || EntityTypes.COD.equals(type) || EntityTypes.EVOKER_FANGS.equals(type) || EntityTypes.TROPICAL_FISH.equals(type) || EntityTypes.FROG.equals(type) || EntityTypes.COPPER_GOLEM.equals(type)) {
|
||||
return 0.5f;
|
||||
} else if (EntityTypes.ARMADILLO.equals(type) || EntityTypes.BEE.equals(type) || EntityTypes.PUFFERFISH.equals(type) || EntityTypes.SALMON.equals(type) || EntityTypes.SNOW_GOLEM.equals(type) || EntityTypes.CAVE_SPIDER.equals(type)) {
|
||||
return 0.7f;
|
||||
@@ -87,7 +87,7 @@ public final class BoundingBoxSize {
|
||||
return 0.98f;
|
||||
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.MINECART_ABSTRACT)) {
|
||||
return 0.98f;
|
||||
} else if (EntityTypes.PLAYER.equals(type)) {
|
||||
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
|
||||
return 0.6f;
|
||||
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
|
||||
return 1.4f;
|
||||
@@ -199,7 +199,7 @@ public final class BoundingBoxSize {
|
||||
return 0.1;
|
||||
} else if (EntityTypes.EVOKER.equals(type) || EntityTypes.ILLUSIONER.equals(type) || EntityTypes.PILLAGER.equals(type) || EntityTypes.RAVAGER.equals(type) || EntityTypes.VINDICATOR.equals(type) || EntityTypes.WITCH.equals(type)) {
|
||||
return -0.45;
|
||||
} else if (EntityTypes.PLAYER.equals(type)) {
|
||||
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
|
||||
return -0.35;
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ public final class BoundingBoxSize {
|
||||
}
|
||||
|
||||
return 1.8f;
|
||||
} else if (EntityTypes.PLAYER.equals(type)) {
|
||||
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
|
||||
return 1.8f;
|
||||
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
|
||||
return 1.4f;
|
||||
@@ -405,6 +405,8 @@ public final class BoundingBoxSize {
|
||||
return 1.8F;
|
||||
} else if (EntityTypes.FIREWORK_ROCKET.equals(type)) {
|
||||
return 0.25F;
|
||||
} else if (EntityTypes.COPPER_GOLEM.equals(type)) {
|
||||
return 1.0F;
|
||||
}
|
||||
return 1.95f;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FluidTypeFlowing {
|
||||
if ((state.getType() == StateTypes.WATER || state.getType() == StateTypes.LAVA) && state.getLevel() >= 8) {
|
||||
for (BlockFace enumdirection : new BlockFace[]{BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}) {
|
||||
if (isSolidFace(player, originalX, originalY, originalZ, enumdirection) || isSolidFace(player, originalX, originalY + 1, originalZ, enumdirection)) {
|
||||
vec3d = normalizeVectorWithoutNaN(vec3d).add(new Vector3dm(0.0D, -6.0D, 0.0D));
|
||||
vec3d = normalizeVectorWithoutNaN(vec3d).add(0.0D, -6.0D, 0.0D);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,17 @@ public class GetBoundingBox {
|
||||
double minZ = centerZ - (width / 2f);
|
||||
double maxZ = centerZ + (width / 2f);
|
||||
|
||||
return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ, false);
|
||||
// it's possible for width and/or height to be negative,
|
||||
// correct the order of min and max
|
||||
return new SimpleCollisionBox(
|
||||
Math.min(minX, maxX),
|
||||
Math.min(minY, maxY),
|
||||
Math.min(minZ, maxZ),
|
||||
Math.max(minX, maxX),
|
||||
Math.max(minY, maxY),
|
||||
Math.max(minZ, maxZ),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public static double @NotNull [] getEntityDimensions(GrimPlayer player, @NotNull PacketEntity entity) {
|
||||
@@ -67,11 +77,20 @@ public class GetBoundingBox {
|
||||
double height = dimensions[1];
|
||||
double halfDepth = dimensions[2] / 2.0;
|
||||
|
||||
box.minX -= halfWidth;
|
||||
box.minY -= 0; // No downward expansion
|
||||
box.minZ -= halfDepth;
|
||||
box.maxX += halfWidth;
|
||||
box.maxY += height;
|
||||
box.maxZ += halfDepth;
|
||||
double minX = box.minX - halfWidth;
|
||||
double minY = box.minY; // No downward expansion
|
||||
double minZ = box.minZ - halfDepth;
|
||||
double maxX = box.maxX + halfWidth;
|
||||
double maxY = box.maxY + height;
|
||||
double maxZ = box.maxZ + halfDepth;
|
||||
|
||||
// it's possible for width and/or height to be negative,
|
||||
// correct the order of min and max
|
||||
box.minX = Math.min(minX, maxX);
|
||||
box.minY = Math.min(minY, maxY);
|
||||
box.minZ = Math.min(minZ, maxZ);
|
||||
box.maxX = Math.max(minX, maxX);
|
||||
box.maxY = Math.max(minY, maxY);
|
||||
box.maxZ = Math.max(minZ, maxZ);
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,9 @@ public class JumpPower {
|
||||
if (player.isSprinting) {
|
||||
float radRotation = GrimMath.radians(player.yaw);
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)) {
|
||||
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2, 0.0, player.trigHandler.cos(radRotation) * 0.2));
|
||||
vector.add(-player.trigHandler.sin(radRotation) * 0.2, 0.0, player.trigHandler.cos(radRotation) * 0.2);
|
||||
} else {
|
||||
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2F, 0.0, player.trigHandler.cos(radRotation) * 0.2F));
|
||||
vector.add(-player.trigHandler.sin(radRotation) * 0.2F, 0.0, player.trigHandler.cos(radRotation) * 0.2F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +179,8 @@ public class ReachUtils {
|
||||
if (giveMovementThresholdLenience) targetBox.expand(player.getMovementThreshold());
|
||||
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
|
||||
for (double eyes : possibleEyeHeights) {
|
||||
Vector3dm from = new Vector3dm(player.x, player.y + eyes, player.z);
|
||||
Vector3dm closestPoint = VectorUtils.cutBoxToVector(from, targetBox);
|
||||
lowest = Math.min(lowest, closestPoint.distance(from));
|
||||
Vector3dm closestPoint = VectorUtils.cutBoxToVector(player.x, player.y + eyes, player.z, targetBox);
|
||||
lowest = Math.min(lowest, closestPoint.distance(player.x, player.y + eyes, player.z));
|
||||
}
|
||||
|
||||
return lowest;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package ac.grim.grimac.utils.nmsutil;
|
||||
|
||||
import ac.grim.grimac.checks.impl.combat.Reach;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.collisions.CollisionData;
|
||||
import ac.grim.grimac.utils.collisions.HitboxData;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.ComplexCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.data.BlockHitData;
|
||||
import ac.grim.grimac.utils.data.EntityHitData;
|
||||
import ac.grim.grimac.utils.data.HitData;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
|
||||
@@ -16,6 +22,8 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -24,7 +32,7 @@ import java.util.function.BiFunction;
|
||||
|
||||
@UtilityClass
|
||||
public class WorldRayTrace {
|
||||
public static HitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
|
||||
public static BlockHitData getNearestBlockHitResult(GrimPlayer player, StateType heldItem, boolean sourcesHaveHitbox, boolean fluidPlacement, boolean itemUsePlacement) {
|
||||
Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z);
|
||||
Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ());
|
||||
Ray trace = new Ray(player, startingPos.getX(), startingPos.getY(), startingPos.getZ(), player.yaw, player.pitch);
|
||||
@@ -59,7 +67,7 @@ public class WorldRayTrace {
|
||||
}
|
||||
}
|
||||
if (bestHitLoc != null) {
|
||||
return new HitData(vector3i, bestHitLoc, bestFace, block);
|
||||
return new BlockHitData(vector3i, bestHitLoc, bestFace, block);
|
||||
}
|
||||
|
||||
if (sourcesHaveHitbox &&
|
||||
@@ -72,7 +80,7 @@ public class WorldRayTrace {
|
||||
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
|
||||
|
||||
if (intercept.first() != null) {
|
||||
return new HitData(vector3i, intercept.first(), intercept.second(), block);
|
||||
return new BlockHitData(vector3i, intercept.first(), intercept.second(), block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +93,7 @@ public class WorldRayTrace {
|
||||
//
|
||||
// I do have to admit that I'm starting to like bifunctions/new java 8 things more than I originally did.
|
||||
// although I still don't understand Mojang's obsession with streams in some of the hottest methods... that kills performance
|
||||
public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction<WrappedBlockState, Vector3i, HitData> predicate) {
|
||||
public static BlockHitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, BiFunction<WrappedBlockState, Vector3i, BlockHitData> predicate) {
|
||||
// I guess go back by the collision epsilon?
|
||||
double endX = GrimMath.lerp(-1.0E-7D, end.x, start.x);
|
||||
double endY = GrimMath.lerp(-1.0E-7D, end.y, start.y);
|
||||
@@ -101,7 +109,7 @@ public class WorldRayTrace {
|
||||
if (start.equals(end)) return null;
|
||||
|
||||
WrappedBlockState state = player.compensatedWorld.getBlock(floorStartX, floorStartY, floorStartZ);
|
||||
HitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
|
||||
BlockHitData apply = predicate.apply(state, new Vector3i(floorStartX, floorStartY, floorStartZ));
|
||||
|
||||
if (apply != null) {
|
||||
return apply;
|
||||
@@ -150,4 +158,162 @@ public class WorldRayTrace {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static HitData getNearestHitResult(GrimPlayer player, PacketEntity targetEntity, Vector3dm eyePos, Vector3dm lookVec) {
|
||||
|
||||
double maxAttackDistance = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
|
||||
double maxBlockDistance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
|
||||
|
||||
Vector3d startingPos = new Vector3d(eyePos.getX(), eyePos.getY(), eyePos.getZ());
|
||||
Vector3dm startingVec = new Vector3dm(startingPos.getX(), startingPos.getY(), startingPos.getZ());
|
||||
Ray trace = new Ray(eyePos, lookVec);
|
||||
Vector3dm endVec = trace.getPointAtDistance(maxBlockDistance);
|
||||
Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ());
|
||||
|
||||
// Get block hit
|
||||
BlockHitData blockHitData = getTraverseResult(player, null, startingPos, startingVec, trace, endPos, false, true, maxBlockDistance, true);
|
||||
Vector3dm closestHitVec = null;
|
||||
PacketEntity closestEntity = null;
|
||||
double closestDistanceSquared = blockHitData != null ? blockHitData.getBlockHitLocation().distanceSquared(startingVec) : maxAttackDistance * maxAttackDistance;
|
||||
|
||||
for (PacketEntity entity : player.compensatedEntities.entityMap.values().stream().filter(PacketEntity::canHit).toList()) {
|
||||
SimpleCollisionBox box = null;
|
||||
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
|
||||
// Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong.
|
||||
|
||||
if (entity.equals(targetEntity)) {
|
||||
box = entity.getPossibleCollisionBoxes();
|
||||
box.expand(player.checkManager.getPacketCheck(Reach.class).threshold);
|
||||
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
|
||||
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
|
||||
//
|
||||
// Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9))
|
||||
box.expand(player.getMovementThreshold());
|
||||
if (ReachUtils.isVecInside(box, eyePos)) {
|
||||
return new EntityHitData(entity, eyePos);
|
||||
}
|
||||
} else {
|
||||
CollisionBox b = entity.getMinimumPossibleCollisionBoxes();
|
||||
if (b instanceof NoCollisionBox) {
|
||||
continue;
|
||||
}
|
||||
box = (SimpleCollisionBox) b;
|
||||
box.expand(-player.checkManager.getPacketCheck(Reach.class).threshold);
|
||||
// todo, shrink by reachThreshold as well for non-target entities?
|
||||
if (!player.packetStateData.didLastLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9))
|
||||
box.expand(-player.getMovementThreshold());
|
||||
}
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
|
||||
box.expand(0.1f);
|
||||
}
|
||||
|
||||
|
||||
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(Math.sqrt(closestDistanceSquared)));
|
||||
|
||||
if (intercept.first() != null) {
|
||||
double distSquared = intercept.first().distanceSquared(startingVec);
|
||||
if (distSquared < closestDistanceSquared) {
|
||||
closestDistanceSquared = distSquared;
|
||||
closestHitVec = intercept.first();
|
||||
closestEntity = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestEntity == null ? blockHitData : new EntityHitData(closestEntity, closestHitVec);
|
||||
}
|
||||
|
||||
// Checks if it was possible to hit a target entity
|
||||
// TODO refactor to return list of rays and why each of them didn't hit instead of closest obstruction
|
||||
// NOTE: It should be impossible for the returned Pair to be null
|
||||
// because all of the possibleLookVecsAndEyeHeights passed in should be ones that hit the target entity
|
||||
// in previous parts of this check when we didn't check for any obstructions like blocks/entities
|
||||
public static @NotNull Pair<@NotNull Double, @NotNull HitData> didRayTraceHit(GrimPlayer player, PacketEntity targetEntity,
|
||||
List<Pair<Vector3dm, Double>> possibleLookVecsAndEyeHeights,
|
||||
Vector3d from) {
|
||||
HitData firstObstruction = null;
|
||||
double firstObstructionDistanceSq = 0;
|
||||
|
||||
// Check every possible look direction and every possible eye height
|
||||
for (Pair<Vector3dm, Double> vectorDoublePair : possibleLookVecsAndEyeHeights) {
|
||||
Vector3dm lookVec = vectorDoublePair.first();
|
||||
double eye = vectorDoublePair.second();
|
||||
|
||||
Vector3dm eyes = new Vector3dm(from.getX(), from.getY() + eye, from.getZ());
|
||||
// this function is completely 0.03 aware
|
||||
final HitData hitResult = WorldRayTrace.getNearestHitResult(player, targetEntity, eyes, lookVec);
|
||||
|
||||
// If we hit the target entity, it's a valid hit
|
||||
if (hitResult instanceof EntityHitData && ((EntityHitData) hitResult).getEntity().equals(targetEntity)) {
|
||||
double distanceSquared = eyes.distanceSquared(hitResult.getBlockHitLocation());
|
||||
return new Pair<>(distanceSquared, hitResult); // Legitimate hit
|
||||
} else if (hitResult != null && firstObstruction == null) {
|
||||
// Store the first obstruction only
|
||||
firstObstruction = hitResult;
|
||||
firstObstructionDistanceSq = eyes.distanceSquared(hitResult.getBlockHitLocation());
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first obstruction if no valid hit found
|
||||
// Since we sort eye heights by likeniness, we should in effect return the most likely (first) obstruction
|
||||
assert firstObstruction != null;
|
||||
return new Pair<>(firstObstructionDistanceSq, firstObstruction);
|
||||
}
|
||||
|
||||
// TODO replace shrinkBlocks boolean with a data structure/better way to represent
|
||||
// 1. We have a target block. Shrink everything by movementThreshold except expand target block (we are checking to see if it matches the target block)
|
||||
// 2. We do not have a target block. Shrink everything by movementThreshold()
|
||||
// 3. Do not expand or shrink everything, we do not expect 0.03/0.002 or we legacy example where we want to keep old behaviour
|
||||
private static BlockHitData getTraverseResult(GrimPlayer player, @Nullable StateType heldItem, Vector3d startingPos, Vector3dm startingVec, Ray trace, Vector3d endPos, boolean sourcesHaveHitbox, boolean checkInside, double knownDistance, boolean shrinkBlocks) {
|
||||
return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> {
|
||||
// even though sometimes we are raytracing against a block that is the target block, we pass false to this function because it only applies a change for brewing stands in 1.8
|
||||
CollisionBox data = HitboxData.getBlockHitbox(player, heldItem, player.getClientVersion(), block, false, vector3i.getX(), vector3i.getY(), vector3i.getZ());
|
||||
SimpleCollisionBox[] boxes = new SimpleCollisionBox[ComplexCollisionBox.DEFAULT_MAX_COLLISION_BOX_SIZE];
|
||||
int size = data.downCast(boxes);
|
||||
|
||||
double bestHitResult = Double.MAX_VALUE;
|
||||
Vector3dm bestHitLoc = null;
|
||||
BlockFace bestFace = null;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (shrinkBlocks) boxes[i].expand(-player.getMovementThreshold());
|
||||
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(boxes[i], trace.getOrigin(), trace.getPointAtDistance(knownDistance));
|
||||
if (intercept.first() == null) continue; // No intercept
|
||||
|
||||
Vector3dm hitLoc = intercept.first();
|
||||
|
||||
// If inside a block, return empty result for reach check (don't bother checking this?)
|
||||
if (checkInside && ReachUtils.isVecInside(boxes[i], trace.getOrigin())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hitLoc.distanceSquared(startingVec) < bestHitResult) {
|
||||
bestHitResult = hitLoc.distanceSquared(startingVec);
|
||||
bestHitLoc = hitLoc;
|
||||
bestFace = intercept.second();
|
||||
}
|
||||
}
|
||||
|
||||
if (bestHitLoc != null) {
|
||||
return new BlockHitData(vector3i, bestHitLoc, bestFace, block);
|
||||
}
|
||||
|
||||
if (sourcesHaveHitbox &&
|
||||
(player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ())
|
||||
|| player.compensatedWorld.getLavaFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()) == (8 / 9f))) {
|
||||
double waterHeight = player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ());
|
||||
SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1);
|
||||
|
||||
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(knownDistance));
|
||||
|
||||
if (intercept.first() != null) {
|
||||
return new BlockHitData(vector3i, intercept.first(), intercept.second(), block);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ac.grim.grimac.utils.payload;
|
||||
|
||||
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
|
||||
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
|
||||
import org.jetbrains.annotations.ApiStatus.NonExtendable;
|
||||
import org.jetbrains.annotations.ApiStatus.OverrideOnly;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.github.retrooper.packetevents.netty.buffer.UnpooledByteBufAllocationHelper.buffer;
|
||||
import static com.github.retrooper.packetevents.netty.buffer.UnpooledByteBufAllocationHelper.wrappedBuffer;
|
||||
import static com.github.retrooper.packetevents.wrapper.PacketWrapper.createUniversalPacketWrapper;
|
||||
|
||||
public interface Payload {
|
||||
@NonExtendable
|
||||
default byte[] write() {
|
||||
Object buffer = buffer();
|
||||
write(createUniversalPacketWrapper(buffer));
|
||||
return ByteBufHelper.array(buffer);
|
||||
}
|
||||
|
||||
@OverrideOnly
|
||||
void write(PacketWrapper<?> wrapper);
|
||||
|
||||
static @NotNull PacketWrapper<?> wrapper(byte[] data) {
|
||||
return createUniversalPacketWrapper(wrappedBuffer(data));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 179 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user