Compare commits

..
Author SHA1 Message Date
Axionize 4933fd9d4f feat: add breakpoint-tolerant clock system and refactor GrimPlayer time handling
This commit Implement zero-cost breakpoint-tolerant time tracking.

It introduces a new time architecture for GrimPlayer that allows developers to pause execution at breakpoints without triggering Timer, FastBreak, or Velocity violations upon resumption.

Changes:
- Refactored `GrimPlayer` to use `nano()`/`now()` overrides instead of direct System calls.
- Added `DebugGrimPlayer` subclass which uses `ThreadLocal` to track intra-packet execution time.
- Logic automatically absorbs execution pauses >200ms (breakpoints) while correctly preserving inter-packet delays (server lag/AFK).
- Added `processingPacket()` hooks to the Packet Dispatcher and Tick Manager to manage thread-local timers.
- Performance impact is 0 in production due to JIT monomorphic inlining and dead code elimination of the base class methods.
2025-11-27 20:23:11 -05:00
Nils Gereke c2c044f61b feat: support for postgresql (#2368)
* feat: support for postgresql
feat: fabric now includes jdbc driver

* fix: missing auto commit

* chore: remove database drivers

* feat: update de and en configs

* feat: support for other languages
2025-11-25 10:31:46 -05:00
Axionize 2d785fdc2c Bump PacketEvents version 2025-11-23 18:59:59 -05:00
Nils Gereke 403fd36830 fix(fabric): world isLoaded throws method not found (#2372)
fix(fabric): world name is always 'world'
2025-11-23 18:14:52 -05:00
Greened e386b936ff Fixed Reach false positive with Happy Ghast and clients under 1.21.6 (#2352) 2025-11-22 06:14:32 -05:00
GroupXyz 3fc985c8f3 Fix spectate command resetting gamemode when spectating someone in another dimension (#2365)
* Fix gamemode change on dimension change 

This issue happened when using the spectate command on a player in another dimension. the spectator was set to survival mode.

* Fix player gamemode reset bug

When spectating a player in another dimension, you would previously get set to survival mode, this is now fixed.
2025-11-22 06:11:15 -05:00
ManInMyVan 499bf7e2f9 more consistent issue templates 2025-11-19 17:02:06 -07:00
ManInMyVan ba91251307 improve bypass template 2025-11-19 16:39:03 -07:00
SamB440 8ddd7f18a5 Setback changes 2025-11-19 13:42:40 +00:00
cyramek 3f67272721 optimise bounding box calculations 2025-11-18 23:04:02 +01:00
AoElite 3380d7f9fd remove some unnecessary publishing paths 2025-11-18 11:14:20 -05:00
tenki-cloud[bot] e1bd2edc7c ci: migrate workflows to tenki (#2362)
* chore: migrate runner to tenki

* chore: migrate runner to tenki

---------

Co-authored-by: tenki-cloud[bot] <204909232+tenki-cloud[bot]@users.noreply.github.com>
2025-11-18 11:00:08 -05:00
Axionize 9b500a2617 Bump PacketEvents version 2025-11-13 03:39:44 -05:00
Weigurde 6df484bcc6 Update CrashC.java (#2350) 2025-11-11 17:10:24 -07:00
cyramek a46d29620f make sure that we use packet-based inventory state after closing the inventory 2025-11-09 23:28:48 +01:00
ManInMyVan b14628c578 hopefully make installation instructions more clear
also moves it higher on the page
2025-11-09 11:39:53 -07:00
AoElite 5c715315c9 tweak GrimDump output and include some non-default alert settings 2025-11-09 06:34:07 -05:00
AoElite ba1d1dbc16 small piston optimization 2025-11-09 05:39:44 -05:00
ManInMyVan def21633e2 disable ExploitB "invalid page characters" check 2025-11-03 19:54:02 -07:00
ItzBloxxy 918e6575e8 New translation ro.yml (#2285)
Config Romanian translation for grim ac.
2025-11-03 17:02:42 +00:00
SamB440 ea1f1615b6 Disable PacketOrderP, fixes #2327 2025-11-03 16:59:53 +00:00
SamB440 c323cf06ce Bump cloud to beta.13, fixes #2318 2025-11-03 16:30:53 +00:00
SamB440 0ba331780e Don't simulate movement in unloaded chunks?
Who knows what other problems this could cause...
2025-11-03 16:26:30 +00:00
ManInMyVan fc47c5b663 refactor via 1.11->1.10 chat hook 2025-11-01 17:10:11 -06:00
ManInMyVan 17cf067d58 fix #2330 2025-11-01 16:05:03 -06:00
ManInMyVan 6280b28e16 show size for applicable reach/hitbox flags 2025-11-01 12:23:17 -06:00
Axionize bfd2bd0e22 Reorder publication of artifacts to Modrinth and update supported versions 2025-10-31 22:29:16 -04:00
ManInMyVan 0df77dd32a slight cleanup of fabric code 2025-10-31 14:59:26 -06:00
ManInMyVan b373e5f1c4 switch to mojmap for fabric
see https://fabricmc.net/2025/10/31/obfuscation.html
2025-10-31 14:38:23 -06:00
Szypkoo 115c32916f fix: useless player fetch (#2329) 2025-10-31 11:03:27 +00:00
ManInMyVan 5f8692deda refactor plugin message wrappers 2025-10-30 17:32:01 -06:00
186 changed files with 2275 additions and 1934 deletions

No files matched your search

+3
View File
@@ -17,3 +17,6 @@ ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_wrap_on_typing = false
[.github/ISSUE_TEMPLATE/*.yml]
indent_size = 2
+10 -10
View File
@@ -5,7 +5,7 @@ body:
- type: textarea
attributes:
label: Describe the bypass and how to replicate it
description: Provide a video, or detailed instructions to help replicate it.
description: Provide a video or detailed instructions to help replicate it.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -17,8 +17,8 @@ body:
attributes:
label: Grim version
description: |
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
validations:
required: true
@@ -26,23 +26,23 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: input
attributes:
label: Client version
description: |
Provide the version of your Minecraft client.
label: Client version
description: |
Provide the version of your Minecraft client. Also provide the protocol version if changed.
validations:
required: true
required: true
- type: textarea
attributes:
label: Plugins
label: Plugins/Mods
description: |
List all plugins or mods running on your server. To list plugins on bukkit, run `/plugins`.
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
+14 -14
View File
@@ -6,7 +6,7 @@ body:
attributes:
label: Stack trace
description: |
Please include the entire stack trace with no attachments. If you would like, you can use a text pasting site such as https://paste.gg.
Provide the entire stack trace with no attachments. If you would like, you can use a text pasting site such as https://mclo.gs.
placeholder: Paste the stack trace or link to the stack trace here
validations:
required: true
@@ -14,7 +14,7 @@ body:
- type: textarea
attributes:
label: How to replicate it if known
description: A video, or detailed instructions to help replicate the issue would help.
description: A video or detailed instructions to help replicate the issue would help.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -26,8 +26,8 @@ body:
attributes:
label: Grim version
description: |
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
validations:
required: true
@@ -35,27 +35,27 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: dropdown
id: type
attributes:
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
validations:
required: true
required: true
- type: textarea
attributes:
label: Plugins/mods
label: Plugins/Mods
description: |
List all plugins or mods running on your server. To list plugins on bukkit, run `/plugins`.
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
+7 -7
View File
@@ -20,8 +20,8 @@ body:
attributes:
label: Grim version
description: |
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
validations:
required: true
@@ -29,7 +29,7 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
@@ -37,16 +37,16 @@ body:
attributes:
label: Client version
description: |
Provide the version of your Minecraft client. You must be able to reproduce the false positive using the vanilla client.
Lunar Client, TLauncher etc. will result in your issue being closed.
Provide the version of your Minecraft client. You must be able to reproduce the false positive using a vanilla client.
Using Lunar Client, TLauncher, Fabric, etc, will result in your issue being closed.
validations:
required: true
- type: textarea
attributes:
label: Plugins
label: Plugins/Mods
description: |
List all plugins or mods running on your server. To list plugins on bukkit, run `/plugins`.
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
+2 -2
View File
@@ -5,6 +5,6 @@ body:
- type: textarea
attributes:
label: Feature description
description: Describe the feature and why it should be added
description: Describe the feature and why it should be added.
validations:
required: true
required: true
+13 -14
View File
@@ -5,7 +5,7 @@ body:
- type: textarea
attributes:
label: Describe the bug and how to replicate it
description: A video, or detailed instructions to help replicate the issue would help.
description: A video or detailed instructions to help replicate the issue would help.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -16,9 +16,8 @@ body:
- type: textarea
attributes:
label: Grim version
description: |
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
description: Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
validations:
required: true
@@ -26,27 +25,27 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: dropdown
id: type
attributes:
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
validations:
required: true
required: true
- type: textarea
attributes:
label: Plugins
label: Plugins/Mods
description: |
List all plugins or mods running on your server. To list plugins on bukkit, run `/plugins`.
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
@@ -19,8 +19,15 @@ body:
- type: input
attributes:
label: Grim Log
description: |
Provide the generated /grim log link.
description: Provide the generated /grim log link.
validations:
required: true
- type: textarea
attributes:
label: Grim version
description: Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
validations:
required: true
@@ -28,7 +35,7 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
@@ -36,16 +43,16 @@ body:
attributes:
label: Client version
description: |
Provide the version of your Minecraft client. You must be able to reproduce the false positive using the vanilla client.
Lunar Client, TLauncher etc. will result in your issue being closed.
Provide the version of your Minecraft client. You must be able to reproduce the false positive using a vanilla client.
Using Lunar Client, TLauncher, Fabric, etc, will result in your issue being closed.
validations:
required: true
- type: textarea
attributes:
label: Plugins
label: Plugins/Mods
description: |
List all plugins or mods running on your server. To list plugins on bukkit, run `/plugins`.
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
+32 -31
View File
@@ -36,7 +36,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: tenki-standard-autoscale
permissions:
contents: read
packages: write
@@ -85,7 +85,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,23 +94,23 @@ 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"
- name: Publish Build Scan URL
# Only run this step if a scan was requested
# Only run this step if a scan was requested
if: ${{ inputs.run-build-scan == true }}
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
@@ -151,7 +151,7 @@ jobs:
- name: Prepare Final Changelog
id: changelog
# Add this 'env' block to safely load the changelogs as environment variables
# Add this 'env' block to safely load the changelogs as environment variables
env:
RELEASE_NOTES: ${{ inputs.release-changelog }}
DEV_NOTES: ${{ steps.git-cliff.outputs.content }}
@@ -165,17 +165,36 @@ 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: Publish Fabric to Modrinth
if: ${{ inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != '' }}
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ secrets.MODRINTH_ID }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
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 }}
version: ${{ env.BUILD_VERSION }}
version-type: ${{ inputs.release-version-type }}
changelog: ${{ steps.changelog.outputs.notes }}
loaders: |
fabric
game-versions: '>=1.21.5 <=1.21.10'
dependencies: 'fabric-api'
#------------------------------------------------------------------
# Publish to Modrinth & API
#------------------------------------------------------------------
@@ -183,7 +202,7 @@ jobs:
if: ${{ inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != '' }}
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ secrets.MODRINTH_ID }}
modrinth-id: ${{ secrets.MODRINTH_ID }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
modrinth-featured: ${{ inputs.is-release-build }} # Only feature release builds
@@ -198,25 +217,7 @@ jobs:
paper
folia
purpur
game-versions: '>=1.7 <=1.21.8'
- name: Publish Fabric to Modrinth
if: ${{ inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != '' }}
uses: Kir-Antipov/mc-publish@v3.3
with:
modrinth-id: ${{ secrets.MODRINTH_ID }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
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 }}
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'
dependencies: 'fabric-api'
game-versions: '>=1.7 <=1.21.10'
- name: Publish to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' }}
@@ -226,4 +227,4 @@ jobs:
-H "User-Agent: GrimACBuilder/v1" \
-H "Authorization: Bearer ${{ secrets.BUILD_UPDATE_TOKEN }}" \
-d '{"org": "${{ github.repository_owner }}", "repository": "${{ github.event.repository.name }}", "branch": "${{ github.ref_name }}", "version": "${{ env.BUILD_VERSION }}"}' \
"${{ secrets.BUILD_UPDATE_URL }}"
"${{ secrets.BUILD_UPDATE_URL }}"
+5 -5
View File
@@ -14,12 +14,12 @@ name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [ dev ]
branches: [dev]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
runs-on: tenki-standard-autoscale
permissions:
actions: read
contents: read
@@ -28,7 +28,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'java-kotlin' ]
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
@@ -60,6 +60,7 @@ jobs:
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -70,6 +71,5 @@ jobs:
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v3
-2
View File
@@ -20,8 +20,6 @@ on:
- 'release'
- 'release/**'
paths:
- '.github/workflows/**'
- 'buildSrc/**'
- 'bukkit/**'
- 'common/**'
- 'fabric/**'
+10 -13
View File
@@ -33,30 +33,27 @@ A premium version is planned, which will offer additional subscription-based pai
- ~~[Hangar](https://hangar.papermc.io/GrimAnticheat/GrimAnticheat)~~
- ~~[SpigotMC](https://www.spigotmc.org/resources/grim-anticheat.99923/)~~
## Requirements & Installation
- Java 17 or higher. *For more details, see [Updating-to-Java-17](https://github.com/GrimAnticheat/Grim/wiki/Updating-to-Java-17).*
- A Spigot, Paper, Folia, or Fabric server environment. *For more details, see [Supported-environments](https://github.com/GrimAnticheat/Grim/wiki/Supported-environments).*
If you use a proxy such as Velocity or Bungeecord:
- If you use Geyser, Floodgate must be installed on the backend server (where Grim is) so Grim can access the Floodgate API.
- If you use ViaVersion, it must be installed on the backend server (where Grim is) ONLY.
Grim does not support having ViaVersion installed on the proxy, even if it is also installed on the backend.
## Resources
- For documentation and examples visit the [Wiki](https://github.com/GrimAnticheat/Grim/wiki).
- For answers to commonly asked questions visit the [FAQ](https://github.com/GrimAnticheat/Grim/wiki/FAQ).
- For community support and project discussion join our [Discord](https://discord.grim.ac).
## Pull Requests
See [Contributing](CONTRIBUTING.md) for more information about contributing and what our guidelines
are.
## Requirements & Installation
- Java 17 or higher. *For more details
see [Updating-to-Java-17](https://github.com/GrimAnticheat/Grim/wiki/Updating-to-Java-17).*
- A Spigot, Paper, Folia, or Fabric server environment. *For more details
see [Supported-environments](https://github.com/GrimAnticheat/Grim/wiki/Supported-environments).*
- If you use Geyser, place Floodgate on the backend server so grim can exempt bedrock players. Grim
cannot access the Floodgate API if it is on the proxy.
- If you use ViaVersion, it should be on the backend server as movement is highly dependent on
client version.
## Developer Plugin API
Grim's plugin API allows you to integrate Grim into your own plugins. Visit
+1 -1
View File
@@ -195,7 +195,7 @@ publishing.publications.create<MavenPublication>("maven") {
tasks {
runServer {
minecraftVersion("1.21.4")
minecraftVersion("1.21.10")
}
shadowJar {
@@ -17,7 +17,7 @@ import java.util.concurrent.CompletableFuture;
public class BukkitGrimEntity implements GrimEntity {
protected static final boolean CAN_USE_DIRECT_GETTERS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_1) && PaperUtils.PAPER;
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;
@@ -5,8 +5,9 @@ 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.TrackedPosition;
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;
@@ -27,11 +28,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(final int aX, final int aY, final int aZ,
final double bX, final double bY, final double bZ) {
return Math.abs(aX - bX) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(aY - bY) <= MAX_VERTICAL_DISTANCE
&& Math.abs(aZ - bZ) <= MAX_HORIZONTAL_DISTANCE;
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;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -71,13 +71,10 @@ public class PistonEvent implements Listener {
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
final BlockFace blockFace = BukkitConversionUtils.fromBukkitFace(event.getDirection());
final int sourceX = piston.getX();
final int sourceY = piston.getY();
final int sourceZ = piston.getZ();
final Vector3i sourcePos = new Vector3i(piston.getX(), piston.getY(), piston.getZ());
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
if (isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos()) && player.compensatedWorld.isChunkLoaded(chunkX, chunkZ)) {
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));
@@ -130,16 +127,12 @@ public class PistonEvent implements Listener {
}
}
final Block block = event.getBlock();
final int chunkX = block.getX() >> 4;
final int chunkZ = block.getZ() >> 4;
final int sourceX = block.getX();
final int sourceY = block.getY();
final int sourceZ = block.getZ();
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());
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
if (isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos()) && player.compensatedWorld.isChunkLoaded(chunkX, chunkZ)) {
int lastTrans = player.lastTransactionSent.get();
PistonData data = new PistonData(face, boxes, lastTrans, false, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
@@ -96,7 +96,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
if (event.isCancelled()) return false;
player.punishmentManager.handleViolation(this);
lastViolationTime = System.currentTimeMillis();
lastViolationTime = player.now();
violations++;
return true;
}
@@ -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) {
@@ -0,0 +1,82 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.protocol.player.User;
import org.jetbrains.annotations.NotNull;
public class DebugGrimPlayer extends GrimPlayer {
private static final long THRESHOLD_NANO = 200_000_000L; // 200ms
private static final long THRESHOLD_MILLIS = 200;
// Global Offsets: Volatile so all threads see the pause adjustment immediately.
// When the Netty thread pauses for 60s, it updates this.
// The Scheduler thread sees the update and "stops" the timeout check.
private volatile long offsetNano = 0;
private volatile long offsetMillis = 0;
// ThreadLocal: Each thread tracks its OWN execution time.
// This prevents the Scheduler Thread (pollData) from resetting the timer
// while the Netty Thread is sitting at a breakpoint.
private final ThreadLocal<Long> packetStartNano = ThreadLocal.withInitial(System::nanoTime);
private final ThreadLocal<Long> packetStartMillis = ThreadLocal.withInitial(System::currentTimeMillis);
public DebugGrimPlayer(@NotNull User user) {
super(user, System.currentTimeMillis(), System.nanoTime());
}
@Override
public void processingPacket() {
// Reset the timer ONLY for the current thread (Netty or Scheduler)
this.packetStartNano.set(System.nanoTime());
this.packetStartMillis.set(System.currentTimeMillis());
}
@Override
public long nano() {
// SAFETY CHECK:
// If something in the parent constructor calls nano() BEFORE we are fully initialized,
// packetStartNano will be null. We must fallback to System time to prevent NPE.
if (packetStartNano == null) return System.nanoTime();
long sysTime = System.nanoTime();
// 1. Get start time for THIS thread
long start = packetStartNano.get();
long executionTime = sysTime - start;
// 2. Check if THIS thread was paused/stuck
if (executionTime > THRESHOLD_NANO) {
// Update the global offset so ALL threads know to skip this time
synchronized (this) {
offsetNano += executionTime;
}
// Reset this thread's timer so we don't loop
packetStartNano.set(sysTime);
} else if (executionTime < 0) {
// Self-healing for rare thread-local init edge cases or system clock jumps
packetStartNano.set(sysTime);
}
// 3. Return System Time minus the Global Pause Offset
return sysTime - offsetNano;
}
@Override
public long now() {
if (packetStartMillis == null) return System.currentTimeMillis();
long sysTime = System.currentTimeMillis();
long start = packetStartMillis.get();
long executionTime = sysTime - start;
if (executionTime > THRESHOLD_MILLIS) {
synchronized (this) {
offsetMillis += executionTime;
}
packetStartMillis.set(sysTime);
}
return sysTime - offsetMillis;
}
}
@@ -0,0 +1,35 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import java.lang.management.ManagementFactory;
public class GrimDebugSettings {
public static boolean shouldEnable() {
// 1. If the argument is disabled, never enable debug mode.
if (!CommonGrimArguments.DEBUG_BREAKPOINT_MODE.value()) {
return false;
}
// 2. Check for Debugger (Startup Agent OR Dynamic Attach)
return isDebuggerAttached();
}
private static boolean isDebuggerAttached() {
// Fast Path: Check startup arguments for -agentlib:jdwp
if (ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("jdwp")) {
return true;
}
// Slow Path (Dynamic Attach): Scan threads for JDWP/Signal Dispatcher
try {
for (Thread t : Thread.getAllStackTraces().keySet()) {
String name = t.getName();
if (name.contains("JDWP")) return true;
}
} catch (Throwable ignored) {}
return false;
}
}
@@ -23,7 +23,7 @@ public class BadPacketsR extends Check implements PacketCheck {
public void onPacketReceive(final PacketReceiveEvent event) {
if (isTransaction(event.getPacketType()) && player.packetStateData.lastTransactionPacketWasValid) {
long ms = (player.getPlayerClockAtLeast() - clock) / 1000000L;
long diff = (System.currentTimeMillis() - lastTransTime);
long diff = (player.now() - lastTransTime);
if (diff > 2000 && ms > 2000) {
if (positions == 0 && clock != 0 && player.gamemode != GameMode.SPECTATOR && !player.compensatedEntities.self.isDead) {
flag("time=" + ms + "ms, " + "lst=" + diff + "ms, positions=" + positions);
@@ -34,7 +34,7 @@ public class BadPacketsR extends Check implements PacketCheck {
player.compensatedWorld.removeInvalidPistonLikeStuff(oldTransId);
positions = 0;
clock = player.getPlayerClockAtLeast();
lastTransTime = System.currentTimeMillis();
lastTransTime = player.now();
oldTransId = player.lastTransactionSent.get();
}
}
@@ -20,8 +20,8 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
private int lastTick;
private boolean didLastFlag;
// Initialize to non-null values to prevent NPE when checking for blockType properties and if position equals old position
private int lastX, lastY, lastZ;
private float lastHardness, lastBlastResistance;
private @NotNull Vector3i lastBreakLoc = new Vector3i();
private @NotNull StateType lastBlockType = StateTypes.AIR;
public AirLiquidBreak(GrimPlayer player) {
super(player);
@@ -40,20 +40,15 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
// I am explicitly making this patch as narrow and specific as possible to potentially discover other blocks that exhibit similar behaviour
int newTick = GrimAPI.INSTANCE.getTickManager().currentTick;
if (lastTick == newTick
&& lastX == blockBreak.x
&& lastY == blockBreak.y
&& lastZ == blockBreak.z
&& lastBreakLoc.equals(blockBreak.position)
&& !didLastFlag
&& lastHardness == 0.0F
&& lastBlastResistance == 0.0F
&& lastBlockType.getHardness() == 0.0F
&& lastBlockType.getBlastResistance() == 0.0F
&& block == StateTypes.WATER
) return;
lastTick = newTick;
lastX = blockBreak.x;
lastY = blockBreak.y;
lastZ = blockBreak.z;
lastHardness = block.getHardness();
lastBlastResistance = block.getBlastResistance();
lastBreakLoc = blockBreak.position;
lastBlockType = block;
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.inventory.getHeldItem().is(ItemTypes.LIGHT) || player.inventory.getOffHand().is(ItemTypes.LIGHT)))
@@ -25,7 +25,7 @@ public class FarBreak extends Check implements BlockBreakCheck {
double min = Double.MAX_VALUE;
for (double d : player.getPossibleEyeHeights()) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
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));
}
@@ -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;
@@ -36,11 +36,8 @@ public class FastBreak extends Check implements BlockBreakCheck {
super(playerData);
}
// The block the player is currently breaking, represented by primitives for performance.
// `targetBlockIsSet` tracks nullness of the position, since primitives can't be null
private boolean targetBlockIsSet;
private int targetBlockX, targetBlockY, targetBlockZ;
// The block the player is currently breaking
Vector3i targetBlockPosition = null;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
@@ -70,15 +67,12 @@ public class FastBreak extends Check implements BlockBreakCheck {
// * can we translate back "up" to server version and run check against server version to avoid loading older registries?
WrappedBlockState block = clientOlderThanServer ? WrappedBlockState.getByGlobalId(player.getClientVersion(), player.getViaTranslatedClientBlockID(blockBreak.block.getGlobalId())) : blockBreak.block;
startBreak = System.currentTimeMillis() - (!targetBlockIsSet ? 50 : 0); // ???
targetBlockIsSet = true;
targetBlockX = blockBreak.x;
targetBlockY = blockBreak.y;
targetBlockZ = blockBreak.z;
startBreak = player.now() - (targetBlockPosition == null ? 50 : 0); // ???
targetBlockPosition = blockBreak.position;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, block);
double breakDelay = System.currentTimeMillis() - lastFinishBreak;
double breakDelay = player.now() - lastFinishBreak;
if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
@@ -93,9 +87,11 @@ public class FastBreak extends Check implements BlockBreakCheck {
}
clampBalance();
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockIsSet) {
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockPosition != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double realTime = player.now() - startBreak;
double diff = predictedTime - realTime;
clampBalance();
@@ -113,7 +109,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
}
// also set start time because the breaking netcode is fucked on 1.14.4+
lastFinishBreak = startBreak = System.currentTimeMillis();
lastFinishBreak = startBreak = player.now();
}
}
@@ -121,8 +117,8 @@ public class FastBreak extends Check implements BlockBreakCheck {
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockIsSet) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockX, targetBlockY, targetBlockZ)));
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockPosition != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockPosition)));
}
}
@@ -21,9 +21,7 @@ public class MultiBreak extends Check implements BlockBreakCheck {
private final List<String> flags = new ArrayList<>();
private boolean hasBroken;
private BlockFace lastFace;
private int lastX;
private int lastY;
private int lastZ;
private Vector3i lastPos;
public MultiBreak(GrimPlayer player) {
super(player);
@@ -35,22 +33,10 @@ public class MultiBreak extends Check implements BlockBreakCheck {
return;
}
final int currentX = blockBreak.x;
final int currentY = blockBreak.y;
final int currentZ = blockBreak.z;
if (hasBroken && (blockBreak.face != lastFace ||
currentX != lastX ||
currentY != lastY ||
currentZ != lastZ)) {
final String currentPosString = MessageUtil.toUnlabeledString(currentX, currentY, currentZ);
final String lastPosString = MessageUtil.toUnlabeledString(lastX, lastY, lastZ);
if (hasBroken && (blockBreak.face != lastFace || !blockBreak.position.equals(lastPos))) {
final String verbose = "face=" + blockBreak.face + ", lastFace=" + lastFace
+ ", pos=" + currentPosString
+ ", lastPos=" + lastPosString;
+ ", pos=" + MessageUtil.toUnlabledString(blockBreak.position)
+ ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
blockBreak.cancel();
@@ -61,9 +47,7 @@ public class MultiBreak extends Check implements BlockBreakCheck {
}
lastFace = blockBreak.face;
lastX = currentX;
lastY = currentY;
lastZ = currentZ;
lastPos = blockBreak.position;
hasBroken = true;
}
@@ -7,7 +7,6 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.Ray;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
@@ -15,7 +14,6 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
@@ -72,7 +70,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
}
private boolean didRayTraceHit(BlockBreak blockBreak) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
@@ -113,8 +111,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
for (Vector3f lookDir : possibleLookDirs) {
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
if (intercept.first() != null) return true;
}
@@ -19,14 +19,7 @@ import static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage;
public class WrongBreak extends Check implements BlockBreakCheck {
private final int exemptedY = player.getClientVersion().isOlderThan(ClientVersion.V_1_8) ? 255 : (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14) ? -1 : 4095);
private boolean lastBlockWasInstantBreak = false;
private boolean lastBlockIsSet;
private int lastBlockX, lastBlockY, lastBlockZ;
private boolean lastCancelledBlockIsSet;
private int lastCancelledBlockX, lastCancelledBlockY, lastCancelledBlockZ;
private boolean lastLastBlockIsSet;
private Vector3i lastBlock, lastCancelledBlock, lastLastBlock = null;
public WrongBreak(final GrimPlayer player) {
super(player);
@@ -34,7 +27,8 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// The client sometimes sends a wierd cancel packet
private boolean shouldExempt(final WrappedBlockState block, int yPos) {
if (lastLastBlockIsSet || !lastBlockIsSet)
// lastLastBlock is always null when this happens, and lastBlock isn't
if (lastLastBlock != null || lastBlock == null)
return false;
// on pre 1.14.4 clients, the YPos of this packet is always the same
@@ -47,29 +41,22 @@ public class WrongBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
if (blockBreak.action == DiggingAction.START_DIGGING) {
final Vector3i pos = blockBreak.position;
lastBlockWasInstantBreak = getBlockDamage(player, blockBreak.block) >= 1;
lastCancelledBlockIsSet = false;
lastCancelledBlock = null;
lastLastBlock = lastBlock;
lastBlock = pos;
}
lastLastBlockIsSet = lastBlockIsSet;
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
final Vector3i pos = blockBreak.position;
lastBlockIsSet = true;
lastBlockX = x;
lastBlockY = y;
lastBlockZ = z;
} else if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
if (!shouldExempt(blockBreak.block, y) && notLastBlock) {
if (!shouldExempt(blockBreak.block, pos.y) && !pos.equals(lastBlock)) {
// https://github.com/GrimAnticheat/Grim/issues/1512
boolean isLastCancelled = lastCancelledBlockIsSet && x == lastCancelledBlockX && y == lastCancelledBlockY && z == lastCancelledBlockZ;
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && isLastCancelled)) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && pos.equals(lastCancelledBlock))) {
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -77,20 +64,18 @@ public class WrongBreak extends Check implements BlockBreakCheck {
}
}
lastCancelledBlockIsSet = true;
lastCancelledBlockX = x;
lastCancelledBlockY = y;
lastCancelledBlockZ = z;
lastCancelledBlock = pos;
lastLastBlock = null;
lastBlock = null;
return;
}
lastLastBlockIsSet = lastBlockIsSet = false;
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
boolean notLastCancelled = !lastCancelledBlockIsSet || x != lastCancelledBlockX || y != lastCancelledBlockY || z != lastCancelledBlockZ;
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
final Vector3i pos = blockBreak.position;
// when a player looks away from the mined block, they send a cancel, and if they look at it again, they don't send another start. (thanks mojang!)
if (notLastCancelled && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && notLastBlock) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (!pos.equals(lastCancelledBlock) && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && !pos.equals(lastBlock)) {
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -99,7 +84,9 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// 1.14.4+ clients don't send another start break in protected regions
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) {
lastCancelledBlockIsSet = lastLastBlockIsSet = lastBlockIsSet = false;
lastCancelledBlock = null;
lastLastBlock = null;
lastBlock = null;
}
}
}
@@ -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;
@@ -63,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();
}
}
}
@@ -22,6 +22,7 @@ import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
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;
@@ -94,7 +95,10 @@ public class Reach extends Check implements PacketCheck {
// TODO: Remove when in front of via
if (entity.type == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8))
return;
//Prevents Happy Ghast Reach false on 1.21.6+ servers with ViaBackwards set up
if (entity.type == EntityTypes.HAPPY_GHAST && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_6)) {
return;
}
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR)
return;
if (player.inVehicle()) return;
@@ -143,10 +147,7 @@ public class Reach extends Check implements PacketCheck {
} else {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) {
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
}
return ReachUtils.getMinReachToBox(player, targetBox) > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
}
@@ -161,10 +162,16 @@ 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);
}
}
@@ -178,10 +185,7 @@ public class Reach extends Check implements PacketCheck {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
}
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
@@ -224,27 +228,21 @@ public class Reach extends Check implements PacketCheck {
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final double eyePosX = from.getX();
final double eyePosZ = from.getZ();
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
for (Vector3dm lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
final double eyePosY = from.getY() + eye;
eyePos.setY(from.getY() + eye);
Vector3dm endReachPos = eyePos.clone().add(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance);
final double endX = eyePosX + lookVec.getX() * distance;
final double endY = eyePosY + lookVec.getY() * distance;
final double endZ = eyePosZ + lookVec.getZ() * distance;
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePosX, eyePosY, eyePosZ, endX, endY, endZ).first();
if (ReachUtils.isVecInside(targetBox, eyePosX, eyePosY, eyePosZ)) {
if (ReachUtils.isVecInside(targetBox, eyePos)) {
minDistance = 0;
break;
}
if (intercept != null) {
minDistance = Math.min(intercept.distance(eyePosX, eyePosY, eyePosZ), minDistance);
minDistance = Math.min(eyePos.distance(intercept), minDistance);
}
}
}
@@ -20,10 +20,9 @@ public class CrashC extends Check implements PacketCheck {
WrapperPlayClientPlayerFlying flying = new WrapperPlayClientPlayerFlying(event);
if (flying.hasPositionChanged()) {
Location pos = flying.getLocation();
if (Double.isNaN(pos.getX()) || Double.isNaN(pos.getY()) || Double.isNaN(pos.getZ())
|| Double.isInfinite(pos.getX()) || Double.isInfinite(pos.getY()) || Double.isInfinite(pos.getZ()) ||
Float.isNaN(pos.getYaw()) || Float.isNaN(pos.getPitch()) ||
Float.isInfinite(pos.getYaw()) || Float.isInfinite(pos.getPitch())) {
if (!Double.isFinite(pos.getX()) || !Double.isFinite(pos.getY()) || !Double.isFinite(pos.getZ())
|| !Float.isFinite(pos.getYaw()) || !Float.isFinite(pos.getPitch())
) {
flagAndAlert("xyzYP: " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ", " + pos.getYaw() + ", " + pos.getPitch());
player.getSetbackTeleportUtil().executeViolationSetback();
event.setCancelled(true);
@@ -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;
}
@@ -15,8 +15,6 @@ public class VehiclePredictionRunner extends Check implements VehicleCheck {
public void process(final VehiclePositionUpdate vehicleUpdate) {
// Vehicle onGround = false always
// We don't do vehicle setbacks because vehicle netcode sucks.
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(
vehicleUpdate.fromX(), vehicleUpdate.fromY(), vehicleUpdate.fromZ(), vehicleUpdate.toX(), vehicleUpdate.toY(), vehicleUpdate.toZ(),
false, null, null, vehicleUpdate.isTeleport()));
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(vehicleUpdate.from(), vehicleUpdate.to(), false, null, null, vehicleUpdate.isTeleport()));
}
}
@@ -42,26 +42,24 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
double offset = predictionComplete.getOffset();
if (listeners.isEmpty() && !outputToConsole) return;
final double predictedX = player.predictedVelocity.vectorX;
final double predictedY = player.predictedVelocity.vectorY;
final double predictedZ = player.predictedVelocity.vectorZ;
if (predictedX == 0.0 && predictedY == 0.0 && predictedZ == 0.0 && offset == 0) return;
if (player.predictedVelocity.vector.lengthSquared() == 0 && offset == 0) return;
String color = pickColor(offset, offset);
Vector3dm predicted = player.predictedVelocity.vector;
Vector3dm actually = player.actualMovement;
String xColor = pickColor(Math.abs(predictedX - actually.getX()), offset);
String yColor = pickColor(Math.abs(predictedY - actually.getY()), offset);
String zColor = pickColor(Math.abs(predictedZ - actually.getZ()), offset);
String xColor = pickColor(Math.abs(predicted.getX() - actually.getX()), offset);
String yColor = pickColor(Math.abs(predicted.getY() - actually.getY()), offset);
String zColor = pickColor(Math.abs(predicted.getZ() - actually.getZ()), offset);
Component p = Component.empty()
.append(P_PREFIX.color(NamedTextColor.NAMES.value(color)))
.append(Component.text(predictedX).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.text(predicted.getX()).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.space())
.append(Component.text(predictedY).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.text(predicted.getY()).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.space())
.append(Component.text(predictedZ).color(NamedTextColor.NAMES.value(zColor)));
.append(Component.text(predicted.getZ()).color(NamedTextColor.NAMES.value(zColor)));
Component a = Component.empty()
.append(A_PREFIX.color(NamedTextColor.NAMES.value(color)))
@@ -59,16 +59,13 @@ public class AirLiquidPlace extends BlockPlaceCheck {
if (player.gamemode == GameMode.CREATIVE) return;
Vector3i blockPos = place.position;
final int x = blockPos.getX();
final int y = blockPos.getY();
final int z = blockPos.getZ();
StateType placeAgainst = player.compensatedWorld.getBlockType(x, y, z);
StateType placeAgainst = player.compensatedWorld.getBlockType(blockPos.getX(), blockPos.getY(), blockPos.getZ());
int currentTick = GrimAPI.INSTANCE.getTickManager().currentTick;
// this is actual more lenient than we need to be, We can check up to 1 ticks for all changes at location sand up to 0 ticks for first change
// But for such tiny differences in legitness it's not worth it.
Iterable<BlockModification> blockModifications = player.blockHistory.getRecentModifications((blockModification) -> currentTick - blockModification.tick() < 2
&& x == blockModification.x() && y == blockModification.y() && z == blockModification.z()
&& blockPos.equals(blockModification.location())
&& (blockModification.cause() == BlockModification.Cause.START_DIGGING || blockModification.cause() == BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION));
// Check if old block from instant breaking in same tick as the current placement was valid
@@ -112,10 +112,10 @@ public class RotationPlace extends BlockPlaceCheck {
for (double d : possibleEyeHeights) {
for (Vector3f lookDir : possibleLookDirs) {
// x, y, z are correct for the block placement even after post tick because of code elsewhere
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
// xRot and yRot are a tick behind
Ray trace = new Ray(player, player.x, player.y + d, player.z, lookDir.getX(), lookDir.getY());
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
if (intercept.first() != null) return true;
}
@@ -12,18 +12,18 @@ public class NegativeTimer extends Timer implements PostPredictionCheck {
public NegativeTimer(GrimPlayer player) {
super(player);
timerBalanceRealTime = System.nanoTime() + clockDrift;
timerBalanceRealTime = player.nano() + clockDrift;
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// We can't negative timer check a 1.9+ player who is standing still.
if (player.uncertaintyHandler.lastPointThree.hasOccurredSince(2) || !predictionComplete.isChecked()) {
timerBalanceRealTime = System.nanoTime() + clockDrift;
timerBalanceRealTime = player.nano() + clockDrift;
}
if (timerBalanceRealTime < lastMovementPlayerClock - clockDrift) {
int lostMS = (int) ((System.nanoTime() - timerBalanceRealTime) / 1e6);
int lostMS = (int) ((player.nano() - timerBalanceRealTime) / 1e6);
flagAndAlertWithSetback("-" + lostMS);
timerBalanceRealTime += 50e6;
}
@@ -14,8 +14,8 @@ public class Timer extends Check implements PacketCheck {
protected long timerBalanceRealTime = 0;
// Default value is real time minus max keep-alive time
protected long knownPlayerClockTime = (long) (System.nanoTime() - 6e10);
protected long lastMovementPlayerClock = (long) (System.nanoTime() - 6e10);
protected long knownPlayerClockTime = (long) (player.nano() - 6e10);
protected long lastMovementPlayerClock = (long) (player.nano() - 6e10);
// How long should the player be able to fall back behind their ping? (nanos)
// Default: 120 milliseconds
@@ -71,7 +71,7 @@ public class Timer extends Check implements PacketCheck {
}
public void doCheck(final PacketReceiveEvent event) {
if (timerBalanceRealTime > System.nanoTime()) {
if (timerBalanceRealTime > player.nano()) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
@@ -19,7 +19,7 @@ public class TimerLimit extends Timer {
@Override
public void doCheck(final PacketReceiveEvent event) {
// 1:1 with Timer minus cancelling the packet
if (timerBalanceRealTime > System.nanoTime()) {
if (timerBalanceRealTime > player.nano()) {
// If timer check already flagged, don't flag.
if (!event.isCancelled()) {
if (flagAndAlert() && shouldSetback()) {
@@ -38,8 +38,8 @@ public class TimerLimit extends Timer {
protected void limitFallBehind() {
// Limit using transaction ping if over 1000ms (default)
long playerClock = lastMovementPlayerClock;
if (limitAbuseOverPing != -1 && System.nanoTime() - playerClock > limitAbuseOverPing) {
playerClock = System.nanoTime() - limitAbuseOverPing;
if (limitAbuseOverPing != -1 && player.nano() - playerClock > limitAbuseOverPing) {
playerClock = player.nano() - limitAbuseOverPing;
}
timerBalanceRealTime = Math.max(timerBalanceRealTime, playerClock - clockDrift);
}
@@ -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) {
@@ -64,7 +60,7 @@ public class KnockbackHandler extends Check implements PostPredictionCheck {
// Blacklist problemated vector until mojang fixes a client-sided bug
if (playerVelocity.getY() == -0.04) {
velocity.setVelocity(playerVelocity.add(0, 1 / 8000D, 0));
velocity.setVelocity(playerVelocity.add(new Vector3d(0, 1 / 8000D, 0)));
playerVelocity = velocity.getVelocity();
event.markForReEncode(true);
}
@@ -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;
@@ -51,9 +51,9 @@ public class GrimDump implements BuildableCommand {
GrimLog.sendLogAsync(sender, generateDump(), string -> link = string, "text/yaml");
}
public static JsonObject getBasicInfo(String type) {
public static JsonObject getDumpInfo() {
JsonObject base = new JsonObject();
base.addProperty("type", type);
base.addProperty("type", "dump");
base.addProperty("timestamp", System.currentTimeMillis());
// versions
JsonObject versions = new JsonObject();
@@ -68,6 +68,11 @@ public class GrimDump implements BuildableCommand {
if (GrimAPI.INSTANCE.isInitialized()) states.addProperty("platform", GrimAPI.INSTANCE.getPlatform().toString());
if (ViaVersionUtil.isAvailable) states.addProperty("has_viaversion", true);
if (PAPER) states.addProperty("has_paper", true);
// include some relevant settings if not default
JsonObject settings = new JsonObject();
if (GrimAPI.INSTANCE.getAlertManager().hasConsoleVerboseEnabled()) settings.addProperty("console_verbose", true);
if (!GrimAPI.INSTANCE.getAlertManager().hasConsoleAlertsEnabled()) settings.addProperty("console_alerts", false);
if (settings.size() > 0) states.add("settings", settings);
// system
JsonObject system = new JsonObject();
base.add("system", system);
@@ -75,8 +80,17 @@ public class GrimDump implements BuildableCommand {
system.addProperty("java_version", System.getProperty("java.version"));
system.addProperty("user_language", System.getProperty("user.language"));
// build
JsonObject build = new JsonObject();
base.add("build", getBuildInfo());
// plugins
JsonArray plugins = new JsonArray();
base.add("plugins", plugins);
for (PlatformPlugin plugin : GrimAPI.INSTANCE.getPluginManager().getPlugins()) {
JsonObject pluginJson = new JsonObject();
pluginJson.addProperty("enabled", plugin.isEnabled());
pluginJson.addProperty("name", plugin.getName());
pluginJson.addProperty("version", plugin.getVersion());
plugins.add(pluginJson);
}
return base;
}
@@ -98,17 +112,7 @@ public class GrimDump implements BuildableCommand {
* @return A JSON-formatted string containing the diagnostic dump.
*/
private String generateDump() {
JsonObject base = getBasicInfo("dump");
// plugins
JsonArray plugins = new JsonArray();
base.add("plugins", plugins);
for (PlatformPlugin plugin : GrimAPI.INSTANCE.getPluginManager().getPlugins()) {
JsonObject pluginJson = new JsonObject();
pluginJson.addProperty("enabled", plugin.isEnabled());
pluginJson.addProperty("name", plugin.getName());
pluginJson.addProperty("version", plugin.getVersion());
plugins.add(pluginJson);
}
JsonObject base = getDumpInfo();
return gson.toJson(base);
}
}
@@ -483,8 +483,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.yaw = move.getYaw();
player.pitch = move.getPitch();
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp.getX(), clamp.getY(), clamp.getZ(),
position.getX(), position.getY(), position.getZ(), move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp, position, move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
player.checkManager.onVehiclePositionUpdate(update);
player.packetStateData.receivedSteerVehicle = false;
@@ -496,7 +495,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT) {
WrapperPlayClientPlayerBlockPlacement packet = new WrapperPlayClientPlayerBlockPlacement(event);
player.lastBlockPlaceUseItem = System.currentTimeMillis();
player.lastBlockPlaceUseItem = player.now();
ItemStack placedWith = player.inventory.getHeldItem();
if (packet.getHand() == InteractionHand.OFF_HAND) {
@@ -533,17 +532,14 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.onPacketCancel();
}
Vector3i facePos = new Vector3i(packet.getBlockPosition().getX() + packet.getFace().getModX(), packet.getBlockPosition().getY() + packet.getFace().getModY(), packet.getBlockPosition().getZ() + packet.getFace().getModZ());
// Ends the client prediction introduced in 1.19+
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
player.user.sendPacket(new WrapperPlayServerAcknowledgeBlockChanges(packet.getSequence()));
} else { // The client isn't smart enough to revert changes
final Vector3i blockPosition = packet.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.resyncPosition(x, y ,z);
final BlockFace face = packet.getFace();
player.resyncPosition(x + face.getModX(), y + face.getModY(), z + face.getModZ());
player.resyncPosition(packet.getBlockPosition());
player.resyncPosition(facePos);
}
// Stop inventory desync from cancelling place
@@ -566,7 +562,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
WrapperPlayClientUseItem packet = new WrapperPlayClientUseItem(event);
player.placeUseItemPackets.add(new BlockPlaceSnapshot(packet, player.isSneaking));
player.lastBlockPlaceUseItem = System.currentTimeMillis();
player.lastBlockPlaceUseItem = player.now();
}
// Call the packet checks last as they can modify the contents of the packet
@@ -667,7 +663,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void handleFlying(GrimPlayer player, double x, double y, double z, float yaw, float pitch, boolean hasPosition, boolean hasLook, boolean onGround, TeleportAcceptData teleportData, PacketReceiveEvent event) {
long now = System.currentTimeMillis();
long now = player.now();
if (!hasPosition) {
// This may need to be secured later, although nothing that is very important relies on this
@@ -728,8 +724,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
if (hasPosition) {
Vector3d clampVector = VectorUtils.clampVector(x, y, z);
final PositionUpdate update = new PositionUpdate(player.x, player.y, player.z, x, y ,z, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
Vector3d position = new Vector3d(x, y, z);
Vector3d clampVector = VectorUtils.clampVector(position);
final PositionUpdate update = new PositionUpdate(new Vector3d(player.x, player.y, player.z), position, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
// Stupidity doesn't care about 0.03
if (!player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) {
@@ -762,7 +759,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
private static void handleDigging(GrimPlayer player, PacketReceiveEvent event) {
player.lastBlockBreak = System.currentTimeMillis();
player.lastBlockBreak = player.now();
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
final DiggingAction action = packet.getAction();
@@ -773,15 +770,14 @@ public class CheckManagerListener extends PacketListenerAbstract {
return;
}
final Vector3i blockPosition = packet.getBlockPosition();
final BlockBreak blockBreak = new BlockBreak(player, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
player.checkManager.onBlockBreak(blockBreak);
if (blockBreak.isCancelled()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPosition(blockBreak.x, blockBreak.y, blockBreak.z, packet.getSequence());
player.resyncPosition(blockBreak.position, packet.getSequence());
return;
}
@@ -789,7 +785,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) {
player.compensatedWorld.startPredicting();
player.compensatedWorld.updateBlock(blockBreak.x, blockBreak.y, blockBreak.z, 0);
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.stopPredicting(packet);
}
@@ -799,14 +795,11 @@ public class CheckManagerListener extends PacketListenerAbstract {
// Instant breaking, no damage means it is unbreakable by creative players (with swords)
if (damage >= 1) {
player.compensatedWorld.startPredicting();
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
player.blockHistory.add(
new BlockModification(
player.compensatedWorld.getBlock(x, y, z),
player.compensatedWorld.getBlock(blockBreak.position),
WrappedBlockState.getByGlobalId(0),
x, y, z,
blockBreak.position,
GrimAPI.INSTANCE.getTickManager().currentTick,
BlockModification.Cause.START_DIGGING
)
@@ -814,9 +807,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && Materials.isWaterSource(player.getClientVersion(), blockBreak.block)) {
// Vanilla uses a method to grab water flowing, but as you can't break flowing water
// We can simply treat all waterlogged blocks or source blocks as source blocks
player.compensatedWorld.updateBlock(x, y, z, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion).getGlobalId());
player.compensatedWorld.updateBlock(blockBreak.position, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion));
} else {
player.compensatedWorld.updateBlock(x, y, z, 0);
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
}
player.compensatedWorld.stopPredicting(packet);
}
@@ -44,7 +44,7 @@ public class PacketEntityAction extends PacketListenerAbstract {
break;
case START_FLYING_WITH_ELYTRA:
if (player.onGround || player.lastOnGround) {
player.getSetbackTeleportUtil().executeForceResync();
player.getSetbackTeleportUtil().executeNonSimulatingForceResync();
if (player.platformPlayer != null) {
// Client ignores sneaking, use it to resync
@@ -66,7 +66,7 @@ public class PacketEntityAction extends PacketListenerAbstract {
player.pointThreeEstimator.updatePlayerGliding();
} else {
// A client is flying with a ghost elytra, resync
player.getSetbackTeleportUtil().executeForceResync();
player.getSetbackTeleportUtil().executeNonSimulatingForceResync();
if (player.platformPlayer != null) {
// Client ignores sneaking, use it to resync
player.platformPlayer.setSneaking(!player.platformPlayer.isSneaking());
@@ -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;
@@ -72,7 +72,7 @@ public class PacketPingListener extends PacketListenerAbstract {
if (id <= 0) {
if (player.didWeSendThatTrans.remove(id)) {
player.packetStateData.lastServerTransWasValid = true;
player.transactionsSent.add(new Pair<>(id, System.nanoTime()));
player.transactionsSent.add(new Pair<>(id, player.nano()));
player.lastTransactionSent.getAndIncrement();
}
}
@@ -91,7 +91,7 @@ public class PacketPingListener extends PacketListenerAbstract {
Short shortID = ((short) id);
if (player.didWeSendThatTrans.remove(shortID)) {
player.packetStateData.lastServerTransWasValid = true;
player.transactionsSent.add(new Pair<>(shortID, System.nanoTime()));
player.transactionsSent.add(new Pair<>(shortID, player.nano()));
player.lastTransactionSent.getAndIncrement();
}
}
@@ -228,7 +228,7 @@ public class PacketPlayerDigging extends PacketListenerAbstract {
// do we need to do this with block breaks too?
// Prevent issues if the player switches slots, while lagging, standing still, and is placing blocks
CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, player.now());
if (player.packetStateData.lastSlotSelected != slot) {
if (player.isResetItemUsageOnSlotChange() && GrimAPI.INSTANCE.getItemResetHandler().getItemUsageHand(player.platformPlayer) == InteractionHand.MAIN_HAND) {
@@ -210,7 +210,9 @@ public class PacketPlayerRespawn extends PacketListenerAbstract {
}
player.pose = Pose.STANDING;
player.clientVelocity = new Vector3dm();
player.gamemode = respawn.getGameMode();
if (!GrimAPI.INSTANCE.getSpectateManager().isSpectating(player.uuid)) {
player.gamemode = respawn.getGameMode();
}
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_17)) {
player.compensatedWorld.setDimension(respawn.getDimensionType(), event.getUser());
}
@@ -2,9 +2,10 @@ package ac.grim.grimac.events.packets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.RotationData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
@@ -34,26 +35,22 @@ public class PacketServerTeleport extends PacketListenerAbstract {
WrapperPlayServerPlayerPositionAndLook teleport = new WrapperPlayServerPlayerPositionAndLook(event);
double posX = teleport.getX();
double posY = teleport.getY();
double posZ = teleport.getZ();
final float yaw = teleport.getYaw();
final float pitch = teleport.getPitch();
Vector3d pos = new Vector3d(teleport.getX(), teleport.getY(), teleport.getZ());
// This is the first packet sent to the client which we need to track
if (player.getSetbackTeleportUtil().getRequiredSetBack() == null) {
// Player teleport event gets called AFTER player join event
player.x = posX;
player.y = posY;
player.z = posZ;
player.yaw = yaw;
player.pitch = pitch;
player.x = teleport.getX();
player.y = teleport.getY();
player.z = teleport.getZ();
player.yaw = teleport.getYaw();
player.pitch = teleport.getPitch();
player.lastX = posX;
player.lastY = posY;
player.lastZ = posZ;
player.lastYaw = yaw;
player.lastPitch = pitch;
player.lastX = teleport.getX();
player.lastY = teleport.getY();
player.lastZ = teleport.getZ();
player.lastYaw = teleport.getYaw();
player.lastPitch = teleport.getPitch();
player.pollData();
}
@@ -70,24 +67,24 @@ public class PacketServerTeleport extends PacketListenerAbstract {
relativeZ = teleport.isRelativeFlag(RelativeFlag.Z);
if (relativeX) {
posX += player.x;
pos = pos.add(new Vector3d(player.x, 0, 0));
teleport.setRelative(RelativeFlag.X, false);
}
if (relativeY) {
posY += player.y;
pos = pos.add(new Vector3d(0, player.y, 0));
teleport.setRelative(RelativeFlag.Y, false);
}
if (relativeZ) {
posZ += player.z;
pos = pos.add(new Vector3d(0, 0, player.z));
teleport.setRelative(RelativeFlag.Z, false);
}
if (relativeX || relativeY || relativeZ) {
teleport.setX(posX);
teleport.setY(posY);
teleport.setZ(posZ);
teleport.setX(pos.getX());
teleport.setY(pos.getY());
teleport.setZ(pos.getZ());
event.markForReEncode(true);
}
@@ -127,13 +124,10 @@ public class PacketServerTeleport extends PacketListenerAbstract {
// For some reason teleports on 1.7 servers are offset by 1.62?
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_8))
posY -= 1.62;
pos = pos.withY(pos.getY() - 1.62);
final Vector3d deltaMovement = teleport.getDeltaMovement();
player.getSetbackTeleportUtil().addSentTeleport(posX, posY, posZ,
deltaMovement.getX(), deltaMovement.getY(), deltaMovement.getZ(),
lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId()
);
Location target = new Location(null, pos.getX(), pos.getY(), pos.getZ());
player.getSetbackTeleportUtil().addSentTeleport(target, teleport.getDeltaMovement(), lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId());
}
if (event.getPacketType() == PacketType.Play.Server.PLAYER_ROTATION) {
@@ -163,12 +157,9 @@ public class PacketServerTeleport extends PacketListenerAbstract {
player.sendTransaction();
event.getTasksAfterSend().add(player::sendTransaction);
Vector3d position = new WrapperPlayServerVehicleMove(event).getPosition();
player.vehicleData.vehicleTeleports.add(new VehicleData.VehicleTeleport(
player.vehicleData.vehicleTeleports.add(new Pair<>(
player.lastTransactionSent.get(),
position.getX(),
position.getY(),
position.getZ()
new WrapperPlayServerVehicleMove(event).getPosition()
));
}
}
@@ -31,7 +31,7 @@ public class PacketWorldBorder extends Check implements PacketCheck {
}
public double getCurrentDiameter() {
double d0 = (double) (System.currentTimeMillis() - startTime) / ((double) endTime - startTime);
double d0 = (double) (player.now() - startTime) / ((double) endTime - startTime);
return d0 < 1.0D ? GrimMath.lerp(d0, oldDiameter, newDiameter) : newDiameter;
}
@@ -125,7 +125,7 @@ public class PacketWorldBorder extends Check implements PacketCheck {
private void setLerp(double oldDiameter, double newDiameter, long length) {
this.oldDiameter = oldDiameter;
this.newDiameter = newDiameter;
this.startTime = System.currentTimeMillis();
this.startTime = player.now();
this.endTime = this.startTime + length;
}
}
@@ -77,11 +77,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (player == null) return;
WrapperPlayServerAcknowledgePlayerDigging ack = new WrapperPlayServerAcknowledgePlayerDigging(event);
Vector3i blockPosition = ack.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.compensatedWorld.handleBlockBreakAck(x, y, z, ack.getBlockId(), ack.getAction(), ack.isSuccessful());
player.compensatedWorld.handleBlockBreakAck(ack.getBlockPosition(), ack.getBlockId(), ack.getAction(), ack.isSuccessful());
}
if (event.getPacketType() == PacketType.Play.Server.CHANGE_GAME_STATE) {
@@ -125,7 +121,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (teleports.getFlags().getMask() != 0) {
continue; // Worse that will happen is people will get an extra setback...
}
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocationX() - chunkCenterX) < 16 && Math.abs(teleports.getLocationZ() - chunkCenterZ) < 16);
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocation().getX() - chunkCenterX) < 16 && Math.abs(teleports.getLocation().getZ() - chunkCenterZ) < 16);
}
if (shouldPostTrans) {
@@ -162,7 +158,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
Vector3i blockPosition = blockChange.getBlockPosition();
// Don't spam transactions (block changes are sent in batches)
if (Math.abs(blockPosition.getX() - player.x) < range && Math.abs(blockPosition.getY() - player.y) < range && Math.abs(blockPosition.getZ() - player.z) < range &&
player.lastTransSent + 2 < System.currentTimeMillis())
player.lastTransSent + 2 < player.now())
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.compensatedWorld.updateBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), blockChange.getBlockId()));
@@ -176,7 +172,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
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()) {
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 < player.now()) {
player.sendTransaction();
break;
}
@@ -24,7 +24,7 @@ public class ActionManager extends Check implements PacketCheck {
if (action.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
player.totalFlyingPacketsSent = 0;
attacking = true;
lastAttack = System.currentTimeMillis();
lastAttack = player.now();
}
} else if (isTickPacketIncludingNonMovement(event.getPacketType())) {
player.totalFlyingPacketsSent++;
@@ -33,6 +33,6 @@ public class ActionManager extends Check implements PacketCheck {
}
public boolean hasAttackedSince(long time) {
return System.currentTimeMillis() - lastAttack < time;
return player.now() - lastAttack < time;
}
}
@@ -75,8 +75,10 @@ public class CheckManager {
private final ClassToInstanceMap<BlockPlaceCheck> blockPlaceChecks;
private final ClassToInstanceMap<PostPredictionCheck> postPredictionChecks;
private PacketEntityReplication packetEntityReplication = null;
private final GrimPlayer player;
public CheckManager(GrimPlayer player) {
this.player = player;
packetChecks = new ImmutableClassToInstanceMap.Builder<PacketCheck>()
.put(PacketOrderProcessor.class, player.packetOrderProcessor)
.put(Reach.class, new Reach(player))
@@ -125,7 +127,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))
@@ -294,6 +296,8 @@ public class CheckManager {
}
public void onPacketReceive(final PacketReceiveEvent packet) {
player.processingPacket();
for (PacketCheck check : packetChecks.values()) {
check.onPacketReceive(packet);
}
@@ -309,6 +313,8 @@ public class CheckManager {
}
public void onPacketSend(final PacketSendEvent packet) {
player.processingPacket();
for (PacketCheck check : prePredictionChecks.values()) {
check.onPacketSend(packet);
}
@@ -177,7 +177,7 @@ public class PunishmentManager implements ConfigReloadable {
public void handleViolation(Check check) {
for (PunishGroup group : groups) {
if (group.checks.contains(check)) {
long currentTime = System.currentTimeMillis();
long currentTime = player.now();
group.violations.put(currentTime, check);
// Remove violations older than the defined time in the config
@@ -18,9 +18,9 @@ import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.TeleportAcceptData;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.data.VelocityData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -74,9 +74,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// Grab friction now when we know player on ground and other variables
final double afterTickFrictionX = player.clientVelocity.getX();
final double afterTickFrictionY = player.clientVelocity.getY();
final double afterTickFrictionZ = player.clientVelocity.getZ();
Vector3dm afterTickFriction = player.clientVelocity.clone();
// We must first check if the player has accepted their setback
// If the setback isn't complete, then this position is illegitimate
@@ -84,12 +82,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// The player needs to now wait for their vehicle to go into the right place before getting back in
if (cheatVehicleInterpolationDelay > 0) cheatVehicleInterpolationDelay = 10;
// Teleport, let velocity be reset
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
} else if (requiredSetBack == null || requiredSetBack.isComplete()) {
cheatVehicleInterpolationDelay--;
// No simulation... we can do that later. We just need to know the valid position.
// As we didn't setback here, the new position is known to be safe!
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
}
if (requiredSetBack != null) requiredSetBack.tick();
@@ -102,6 +100,13 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
blockMovementsUntilResync(true, true);
}
public void executeNonSimulatingForceResync() {
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
@@ -125,6 +130,29 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
return player.platformPlayer != null && player.noSetbackPermission;
}
private void simulateFriction(Vector3dm vector) {
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector.multiply(0.5D);
if (player.hasGravity)
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(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
}
// Prevent abusing setbacks to move out of blocks like webs
vector.multiply(player.stuckSpeedMultiplier);
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector, VectorData.VectorType.BestVelPicked))));
}
private void blockMovementsUntilResync(boolean simulateNextTickPosition, boolean isResync) {
if (requiredSetBack == null) return; // Hasn't spawned
if (player.platformPlayer != null && player.noSetbackPermission)
@@ -133,120 +161,69 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
if (isPendingSetback()) return; // Don't spam setbacks
// Only let us full resync once every five seconds to prevent unneeded bukkit load
if (System.currentTimeMillis() - lastWorldResync > 5 * 1000) {
if (player.now() - lastWorldResync > 5 * 1000) {
player.resyncPositions(player.boundingBox.copy().expand(1));
lastWorldResync = System.currentTimeMillis();
lastWorldResync = player.now();
}
double clientVelX = lastKnownGoodPosition.getVectorX();
double clientVelY = lastKnownGoodPosition.getVectorY();
double clientVelZ = lastKnownGoodPosition.getVectorZ();
Vector3dm clientVel = lastKnownGoodPosition.vector.clone();
Pair<VelocityData, Vector3dm> futureKb = player.checkManager.getKnockbackHandler().getFutureKnockback();
VelocityData futureExplosion = player.checkManager.getExplosionHandler().getFutureExplosion();
// Velocity sets
if (futureKb.first() != null) {
clientVelX = futureKb.second().getX();
clientVelY = futureKb.second().getY();
clientVelZ = futureKb.second().getZ();
clientVel = futureKb.second();
}
// Explosion adds
if (futureExplosion != null && (futureKb.first() == null || futureKb.first().transaction < futureExplosion.transaction)) {
clientVelX += futureExplosion.vector.getX();
clientVelY += futureExplosion.vector.getY();
clientVelZ += futureExplosion.vector.getZ();
clientVel.add(futureExplosion.vector);
}
double x = lastKnownGoodPosition.posX;
double y = lastKnownGoodPosition.posY;
double z = lastKnownGoodPosition.posZ;
Vector3d position = lastKnownGoodPosition.pos;
SimpleCollisionBox oldBB = player.boundingBox;
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, x, y, z);
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, position.getX(), position.getY(), position.getZ());
// Mini prediction engine - simulate collisions
if (simulateNextTickPosition) {
Vector3dm collide = Collisions.collide(player, clientVelX, clientVelY, clientVelZ);
Vector3dm collide = Collisions.collide(player, clientVel.getX(), clientVel.getY(), clientVel.getZ());
x += collide.getX();
y += collide.getY();
position = position.withX(position.getX() + collide.getX());
position = position.withY(position.getY() + collide.getY());
// TODO: Is this even needed? Can't reproduce any phasing on vanilla 1.8 when being setback.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
// 1.8 players need the collision epsilon to not phase into blocks when being setback
// Due to simulation, this will not allow a flight bypass by sending a billion invalid movements
y += SimpleCollisionBox.COLLISION_EPSILON;
position = position.withY(position.getY() + SimpleCollisionBox.COLLISION_EPSILON);
}
z += collide.getZ();
position = position.withZ(position.getZ() + collide.getZ());
if (clientVelX != collide.getX()) clientVelX = 0;
if (clientVelY != collide.getY()) clientVelY = 0;
if (clientVelZ != collide.getZ()) clientVelZ = 0;
if (clientVel.getX() != collide.getX()) clientVel.setX(0);
if (clientVel.getY() != collide.getY()) clientVel.setY(0);
if (clientVel.getZ() != collide.getZ()) clientVel.setZ(0);
// BEGIN SIMULATING FRICTION
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
double[] vector = {clientVelX, clientVelY, clientVelZ};
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector[0] *= 0.5D;
vector[1] *= 0.5D;
vector[2] *= 0.5D;
if (player.hasGravity)
vector[1] += -player.gravity / 4.0D;
} else if (player.isGliding) {
Vector3dm getLook = ReachUtils.getLook(player, player.yaw, player.pitch);
PredictionEngineElytra.applyElytraMovementInPlace(player, vector, getLook.getX(), getLook.getY(), getLook.getZ());
vector[0] *= player.stuckSpeedMultiplier.getX() * 0.99F;
vector[1] *= player.stuckSpeedMultiplier.getY() * 0.98F;
vector[2] *= player.stuckSpeedMultiplier.getZ() * 0.99F;
vector[1] -= 0.05; // Make the player fall a bit
} else { // Gliding doesn't have friction, we handle it differently
// Lava and normal movement
vector[0] *= player.friction;
vector[1] = PredictionEngineNormal.staticVectorEndOfTickY(player, vector[1]);
vector[2] *= player.friction;
// Prevent abusing setbacks to move out of blocks like webs
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector[0], vector[1], vector[2], VectorData.VectorType.BestVelPicked))));
// END SIMULATING FRICTION
clientVelX = vector[0];
clientVelY = vector[1];
clientVelZ = vector[2];
simulateFriction(clientVel);
}
player.boundingBox = oldBB; // reset back to the new bounding box
boolean sendVelocity = true;
if (!hasAcceptedSpawnTeleport || player.isFlying)
sendVelocity = false; // if the player is flying or hasn't spawned... don't force kb
clientVel = null; // if the player is flying or hasn't spawned... don't force kb
// Something weird has occurred in the player's movement, block offsets until we resync
if (isResync) {
blockOffsets = true;
}
SetBackData data = new SetBackData(new TeleportData(x, y, z, 0, 0, 0, new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVelX, clientVelY, clientVelZ, sendVelocity, player.inVehicle(), false);
SetBackData data = new SetBackData(new TeleportData(position, new Vector3d(), RelativeFlag.YAW.or(RelativeFlag.PITCH), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVel, player.inVehicle(), false);
sendSetback(data);
}
private void sendSetback(SetBackData data) {
isSendingSetback = true;
double positionX = data.getTeleportData().getLocationX();
double positionY = data.getTeleportData().getLocationY();
double positionZ = data.getTeleportData().getLocationZ();
Vector3d position = data.getTeleportData().getLocation();
try {
// Player is in a vehicle
@@ -262,7 +239,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// Stop the player from being able to teleport vehicles and simply re-enter them to continue,
// therefore, teleport the entity
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(positionX, positionY, positionZ), player.yaw % 360, 0, false));
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(position.getX(), position.getY(), position.getZ()), player.yaw % 360, 0, false));
player.getSetbackTeleportUtil().cheatVehicleInterpolationDelay = Integer.MAX_VALUE; // Set to max until player accepts the new position
// Make sure bukkit also knows the player got teleported out of their vehicle, can't do this async
@@ -277,7 +254,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
}
}
double y = positionY;
double y = position.getY();
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
y += 1.62; // 1.7 teleport offset if grim ever supports 1.7 again
}
@@ -292,15 +269,15 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
data.getTeleportData().setTransaction(player.lastTransactionSent.get());
// Use provided transaction ID to make sure it can never desync, although there's no reason to do this
addSentTeleport(positionX, y, positionZ, 0, 0, 0, data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
addSentTeleport(new Location(null, position.getX(), y, position.getZ(), player.yaw % 360, player.pitch % 360), new Vector3d(), data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
// This must be done after setting the sent teleport, otherwise we lose velocity data
requiredSetBack = data;
// Send after tracking to fix race condition
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(positionX, positionY, positionZ, 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(position.getX(), position.getY(), position.getZ(), 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
player.sendTransaction();
if (data.isHasVelocity() && (data.getVelocityX() != 0 || data.getVelocityY() != 0 || data.getVelocityZ() != 0)) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocityX(), data.getVelocityY(), data.getVelocityZ())));
if (data.getVelocity() != null && data.getVelocity().lengthSquared() > 0) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocity().getX(), data.getVelocity().getY(), data.getVelocity().getZ())));
}
} finally {
isSendingSetback = false;
@@ -320,12 +297,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
TeleportData teleportPos;
while ((teleportPos = pendingTeleports.peek()) != null) {
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocationX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocationY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocationZ();
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocation().getX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocation().getY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocation().getZ();
// There seems to be a version difference in teleports past 30 million... just clamp the vector
Vector3d clamped = VectorUtils.clampVector(trueTeleportX, trueTeleportY, trueTeleportZ);
Vector3d clamped = VectorUtils.clampVector(new Vector3d(trueTeleportX, trueTeleportY, trueTeleportZ));
double threshold = teleportPos.isRelativePos() ? player.getMovementThreshold() : 0;
boolean closeEnoughY = Math.abs(clamped.getY() - y) <= 1e-7 + threshold; // 1.7 rounding
@@ -371,17 +348,18 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
int lastTransaction = player.lastTransactionReceived.get();
while (true) {
VehicleData.VehicleTeleport teleportPos = player.vehicleData.vehicleTeleports.peek();
Pair<Integer, Vector3d> teleportPos = player.vehicleData.vehicleTeleports.peek();
if (teleportPos == null) break;
if (lastTransaction < teleportPos.teleportId()) {
if (lastTransaction < teleportPos.first()) {
break;
}
if (teleportPos.x() == x && teleportPos.y() == y && teleportPos.z() == z) {
Vector3d position = teleportPos.second();
if (position.getX() == x && position.getY() == y && position.getZ() == z) {
player.vehicleData.vehicleTeleports.poll();
return true;
} else if (lastTransaction > teleportPos.teleportId() + 1) {
} else if (lastTransaction > teleportPos.first() + 1) {
player.vehicleData.vehicleTeleports.poll();
// Vehicles have terrible netcode so just ignore it if the teleport wasn't from us setting the player back
@@ -406,7 +384,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
private boolean isPendingSetback() {
// Relative setbacks shouldn't count
if (requiredSetBack.getTeleportData().isRelativeX() || requiredSetBack.getTeleportData().isRelativeY() || requiredSetBack.getTeleportData().isRelativeZ()) {
if (requiredSetBack != null && (requiredSetBack.getTeleportData().isRelativeX() || requiredSetBack.getTeleportData().isRelativeY() || requiredSetBack.getTeleportData().isRelativeZ())) {
return false;
}
// The setback is not complete
@@ -427,37 +405,36 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
!player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport);
}
public void addSentTeleport(double x, double y, double z, double velocityX, double velocityY, double velocityZ, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, flags, transaction, teleportId);
public void addSentTeleport(Location position, Vector3d velocity, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(new Vector3d(position.getX(), position.getY(), position.getZ()), velocity, flags, transaction, teleportId);
pendingTeleports.add(data);
Vector3d safePosition = new Vector3d(position.getX(), position.getY(), position.getZ());
// We must convert relative teleports to avoid them becoming client controlled in the case of setback
if (flags.has(RelativeFlag.X)) {
x += lastKnownGoodPosition.posX;
safePosition = safePosition.withX(safePosition.getX() + lastKnownGoodPosition.pos.getX());
}
if (flags.has(RelativeFlag.Y)) {
y += lastKnownGoodPosition.posY;
safePosition = safePosition.withY(safePosition.getY() + lastKnownGoodPosition.pos.getY());
}
if (flags.has(RelativeFlag.Z)) {
z += lastKnownGoodPosition.posZ;
safePosition = safePosition.withZ(safePosition.getZ() + lastKnownGoodPosition.pos.getZ());
}
data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, false, plugin);
data = new TeleportData(safePosition, velocity, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, null, false, plugin);
this.lastKnownGoodPosition = new SetbackPosWithVector(x, y, z, 0, 0, 0);
this.lastKnownGoodPosition = new SetbackPosWithVector(safePosition, new Vector3dm());
}
@AllArgsConstructor
@Getter
@Setter
public static class SetbackPosWithVector {
private final double posX;
private final double posY;
private final double posZ;
private final double vectorY;
private double vectorX, vectorZ;
private final Vector3d pos;
private Vector3dm vector;
}
}
@@ -6,12 +6,10 @@ import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.predictionengine.UncertaintyHandler;
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.lists.EvictingQueue;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@@ -56,12 +54,7 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
for (Iterator<Object2IntMap.Entry<StringBuilder>> it = continuedDebug.object2IntEntrySet().iterator(); it.hasNext(); ) {
Map.Entry<StringBuilder, Integer> debug = it.next();
appendDebug(debug.getKey(), player.predictedVelocity,
player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(),
location,
player.startTickClientVel.getX(), player.startTickClientVel.getY(), player.startTickClientVel.getZ(),
player.baseTickAddition.getX(), player.baseTickAddition.getY(), player.baseTickAddition.getZ(),
player.baseTickWaterPushing.getX(), player.baseTickWaterPushing.getY(), player.baseTickWaterPushing.getZ());
appendDebug(debug.getKey(), player.predictedVelocity, player.actualMovement, location, player.startTickClientVel, player.baseTickAddition, player.baseTickWaterPushing);
debug.setValue(debug.getValue() - 1);
if (debug.getValue() <= 0) it.remove();
}
@@ -97,14 +90,7 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
Vector3dm startTickVel = startTickClientVel.get(i);
Vector3dm addition = baseTickAddition.get(i);
Vector3dm water = baseTickWater.get(i);
appendDebug(sb,
predict,
actual.getX(), actual.getY(), actual.getZ(),
loc,
startTickVel.getX(), startTickVel.getY(), startTickVel.getZ(),
addition.getX(), addition.getY(), addition.getZ(),
water.getX(), water.getY(), water.getZ()
);
appendDebug(sb, predict, actual, loc, startTickVel, addition, water);
}
UncertaintyHandler uncertaintyHandler = player.uncertaintyHandler;
@@ -208,13 +194,7 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
continuedDebug.put(sb, 40);
}
private void appendDebug(final StringBuilder sb, final VectorData predict,
final double actualX, final double actualY, final double actualZ,
final Location location,
final double startTickX, final double startTickY, final double startTickZ,
final double additionX, final double additionY, final double additionZ,
final double waterX, final double waterY, final double waterZ) {
private void appendDebug(StringBuilder sb, VectorData predict, Vector3dm actual, Location location, Vector3dm startTick, Vector3dm addition, Vector3dm water) {
if (predict.isZeroPointZeroThree()) {
sb.append("Movement threshold/tick skipping\n");
}
@@ -248,45 +228,36 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
}
// Apply 0.003/0.005 to make numbers more accurate
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTickX, startTickY, startTickZ, VectorData.VectorType.BestVelPicked)));
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTick.clone(), VectorData.VectorType.BestVelPicked)));
new PredictionEngine().applyMovementThreshold(player, set);
VectorData tempVectorData = ((VectorData) set.toArray()[0]);
double trueStartVelX = tempVectorData.vectorX;
double trueStartVelY = tempVectorData.vectorY;
double trueStartVelZ = tempVectorData.vectorZ;
Vector3dm trueStartVel = ((VectorData) set.toArray()[0]).vector;
final double offsetX = actualX - trueStartVelX;
final double offsetY = actualY - trueStartVelY;
final double offsetZ = actualZ - trueStartVelZ;
double offsetLength = Math.sqrt(offsetX*offsetX + offsetY*offsetY + offsetZ*offsetZ);
Vec2 clientMovement = getPlayerMathMovement(player, offsetX, offsetZ, location.xRot);
Vec2 simulatedMovement = getPlayerMathMovement(player, predict.vectorX - trueStartVelX, predict.vectorZ - trueStartVelZ, location.xRot);
trueStartVelX += additionX + waterX;
trueStartVelY += additionY + waterY;
trueStartVelZ += additionZ + waterZ;
Vector3dm clientMovement = getPlayerMathMovement(player, actual.clone().subtract(trueStartVel), location.xRot);
Vector3dm simulatedMovement = getPlayerMathMovement(player, predict.vector.clone().subtract(trueStartVel), location.xRot);
Vector3dm offset = actual.clone().subtract(predict.vector);
trueStartVel.add(addition);
trueStartVel.add(water);
sb.append("Simulated: ");
sb.append(MessageUtil.toUnlabeledString(trueStartVelX, trueStartVelY, trueStartVelZ));
sb.append(predict.vector.toString());
sb.append("\nActually: ");
sb.append(MessageUtil.toUnlabeledString(actualX, actualY, actualZ));
sb.append(actual);
sb.append("\nOffset Vector: ");
sb.append(MessageUtil.toUnlabeledString(offsetX, offsetY, offsetZ));
sb.append(offset);
sb.append("\nOffset: ");
sb.append(offsetLength);
sb.append(offset.length());
sb.append("\nLocation: ");
sb.append(location);
sb.append("\nInitial velocity: ");
sb.append(MessageUtil.toUnlabeledString(startTickX, startTickY, startTickZ));
sb.append(startTick);
if (additionX != 0 || additionY != 0 || additionZ != 0) {
if (addition.lengthSquared() > 0) {
sb.append("\nInitial vel addition: ");
sb.append(MessageUtil.toUnlabeledString(additionX, additionY, additionZ));
sb.append(addition);
}
if (waterX != 0 || waterY != 0 || waterZ != 0) {
if (water.lengthSquared() > 0) {
sb.append("\nWater vel addition: ");
sb.append(MessageUtil.toUnlabeledString(waterX, waterY, waterZ));
sb.append(water);
}
sb.append("\nClient input: ");
@@ -302,14 +273,14 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
sb.append("\n\n");
}
private Vec2 getPlayerMathMovement(GrimPlayer player, double x, double z, float f2) {
private Vector3dm getPlayerMathMovement(GrimPlayer player, Vector3dm wantedMovement, float f2) {
float f3 = player.trigHandler.sin(f2 * 0.017453292f);
float f4 = player.trigHandler.cos(f2 * 0.017453292f);
float bestTheoreticalX = (float) (f3 * z + f4 * x) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * x + f4 * z) / (f3 * f3 + f4 * f4);
float bestTheoreticalX = (float) (f3 * wantedMovement.getZ() + f4 * wantedMovement.getX()) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * wantedMovement.getX() + f4 * wantedMovement.getZ()) / (f3 * f3 + f4 * f4);
return new Vec2(bestTheoreticalX, bestTheoreticalZ);
return new Vector3dm(bestTheoreticalX, 0, bestTheoreticalZ);
}
private record Location(double x, double y, double z, float xRot, float yRot, String world) {
@@ -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();
@@ -6,6 +6,7 @@ import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.manager.init.ReloadableInitable;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.manager.violationdatabase.mysql.MySQLViolationDatabase;
import ac.grim.grimac.manager.violationdatabase.postgresql.PostgresqlViolationDatabase;
import ac.grim.grimac.manager.violationdatabase.sqlite.SQLiteViolationDatabase;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.LogUtil;
@@ -93,6 +94,28 @@ public class ViolationDatabaseManager implements StartableInitable, ReloadableIn
}
}
case "POSTGRESQL" -> {
String host = cfg.getStringElse("history.database.host", "localhost:3306");
String db = cfg.getStringElse("history.database.database", "grimac");
String user = cfg.getStringElse("history.database.username", "root");
String pwd = cfg.getStringElse("history.database.password", "password");
if (database instanceof PostgresqlViolationDatabase postgresql
&& postgresql.sameConfig(host, db, user, pwd)) {
break; // nothing changed → keep pool
}
database.disconnect();
database = new PostgresqlViolationDatabase(host, db, user, pwd);
try {
database.connect();
loaded = true;
} catch (SQLException e) {
LogUtil.error(e);
this.database = NoOpViolationDatabase.INSTANCE;
loaded = false;
}
}
default -> { // NOOP or invalid
if (!(database instanceof NoOpViolationDatabase)) {
database.disconnect();
@@ -0,0 +1,31 @@
package ac.grim.grimac.manager.violationdatabase.postgresql;
import ac.grim.grimac.manager.violationdatabase.DatabaseDialect;
public class PostgresqlDialect implements DatabaseDialect {
@Override
public String getUuidColumnType() {
return "UUID";
}
@Override
public String getAutoIncrementPrimaryKeySyntax() {
return "BIGSERIAL PRIMARY KEY";
}
@Override
public String getInsertOrIgnoreSyntax(String tableName, String columnNames) {
return "INSERT INTO " + tableName + " (" + columnNames + ") VALUES (?) ON CONFLICT DO NOTHING";
}
@Override
public String getUniqueConstraintViolationSQLState() {
return "23505"; // Postgresql duplicate key error
}
@Override
public int getUniqueConstraintViolationErrorCode() {
return 0; // Postgresql is not using numbers
}
}
@@ -0,0 +1,286 @@
package ac.grim.grimac.manager.violationdatabase.postgresql;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.manager.violationdatabase.*;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.LogUtil;
import com.github.retrooper.packetevents.PacketEvents;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
public class PostgresqlViolationDatabase implements ViolationDatabase {
private static String quoteVerboseColumn() {
return "\"" + DatabaseConstants.VIOLATIONS_VERBOSE_COLUMN + "\"";
}
private HikariDataSource dataSource;
private final DatabaseDialect dialect;
public PostgresqlViolationDatabase(String url, String database, String username, String password) {
this.dialect = new PostgresqlDialect();
setupDataSource(url, database, username, password);
}
private void setupDataSource(String url, String database, String username, String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://" + url + "/" + database);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(10);
config.setAutoCommit(true);
dataSource = new HikariDataSource(config);
}
@Override
public void connect() throws SQLException {
try (Connection connection = dataSource.getConnection()) {
String pkSyntax = dialect.getAutoIncrementPrimaryKeySyntax();
String uuidType = dialect.getUuidColumnType();
// 1. Create Lookup Table for Server Names
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.SERVERS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.SERVERS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.SERVERS_TABLE + "_name ON " + DatabaseConstants.SERVERS_TABLE + "(" + DatabaseConstants.SERVERS_STRING_COLUMN + ");"
).execute();
// 2. Create Lookup Table for Check Names
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.CHECK_NAMES_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.CHECK_NAMES_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CHECK_NAMES_TABLE + "_string ON " + DatabaseConstants.CHECK_NAMES_TABLE + "(" + DatabaseConstants.CHECK_NAMES_STRING_COLUMN + ");"
).execute();
// --- NEW LOOKUP TABLES ---
// 3. Create Lookup Table for Grim Versions
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.GRIM_VERSIONS_TABLE + "_string ON " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(" + DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN + ");"
).execute();
// 4. Create Lookup Table for Client Brands
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CLIENT_BRANDS_TABLE + "_string ON " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(" + DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN + ");"
).execute();
// 5. Create Lookup Table for Client Versions
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.CLIENT_VERSIONS_TABLE + "_string ON " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(" + DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN + ");"
).execute();
// 6. Create Lookup Table for Server Versions
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN + " VARCHAR(255) NOT NULL UNIQUE" +
")"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.SERVER_VERSIONS_TABLE + "_string ON " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(" + DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN + ");"
).execute();
// --- END NEW LOOKUP TABLES ---
// 7. Create Main Violations Table with ALL Foreign Keys and optimized UUID
connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS " + DatabaseConstants.VIOLATIONS_TABLE + "(" +
"id " + pkSyntax + ", " +
DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_UUID_COLUMN + " " + uuidType + " NOT NULL, " +
DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + " BIGINT NOT NULL, " +
quoteVerboseColumn() + " TEXT NOT NULL, " +
DatabaseConstants.VIOLATIONS_VL_COLUMN + " INT NOT NULL, " +
DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + " BIGINT NOT NULL, " +
DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + " BIGINT NOT NULL, " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + ") REFERENCES " + DatabaseConstants.SERVERS_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CHECK_NAMES_TABLE + "(id), " +
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.GRIM_VERSIONS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_BRANDS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.CLIENT_VERSIONS_TABLE + "(id), " + // NEW
"FOREIGN KEY (" + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + ") REFERENCES " + DatabaseConstants.SERVER_VERSIONS_TABLE + "(id)" + // NEW
")"
).execute();
// 8. Create Indexes for efficient querying on main table (includes new FKs)
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_uuid ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_UUID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_created_at ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_server_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_check_name_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + ");"
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_grim_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_client_brand_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_client_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ");" // NEW
).execute();
connection.prepareStatement(
"CREATE INDEX IF NOT EXISTS idx_" + DatabaseConstants.VIOLATIONS_TABLE + "_server_version_id ON " + DatabaseConstants.VIOLATIONS_TABLE + "(" + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + ");" // NEW
).execute();
} catch (SQLException ex) {
LogUtil.error("Failed to generate violations database:", ex);
throw ex;
}
}
@Override
// Updated method signature to accept all new parameters
public synchronized void logAlert(GrimPlayer player, String grimVersion, String verbose, String checkName, int vls) {
try (Connection connection = dataSource.getConnection();
// Updated INSERT statement with all new columns
PreparedStatement insertAlert = connection.prepareStatement(
"INSERT INTO " + DatabaseConstants.VIOLATIONS_TABLE + " (" +
DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + ", " +
DatabaseConstants.VIOLATIONS_UUID_COLUMN + ", " +
DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + ", " +
quoteVerboseColumn() + ", " +
DatabaseConstants.VIOLATIONS_VL_COLUMN + ", " +
DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + ", " +
DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + ", " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + ", " + // NEW
DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + ", " + // NEW
DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + // NEW
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" // Total 10 parameters now
)
) {
// Get or create IDs for all deduplicated strings
String serverName = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("history.server-name", "Prison");
long serverId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.SERVERS_TABLE, DatabaseConstants.SERVERS_STRING_COLUMN, serverName);
long checkNameId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.CHECK_NAMES_TABLE, DatabaseConstants.CHECK_NAMES_STRING_COLUMN, checkName);
long grimVersionId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.GRIM_VERSIONS_TABLE, DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN, grimVersion);
long clientBrandId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.CLIENT_BRANDS_TABLE, DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN, player.getBrand());
long clientVersionId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.CLIENT_VERSIONS_TABLE, DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN, player.getClientVersion().getReleaseName());
long serverVersionId = DatabaseUtils.getOrCreateId(connection, dialect, DatabaseConstants.SERVER_VERSIONS_TABLE, DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN, PacketEvents.getAPI().getServerManager().getVersion().toString());
// Set parameters for the PreparedStatement
insertAlert.setLong(1, serverId);
insertAlert.setObject(2, player.getUniqueId());
insertAlert.setLong(3, checkNameId);
insertAlert.setString(4, verbose);
insertAlert.setInt(5, vls);
insertAlert.setLong(6, System.currentTimeMillis());
insertAlert.setLong(7, grimVersionId); // NEW
insertAlert.setLong(8, clientBrandId); // NEW
insertAlert.setLong(9, clientVersionId); // NEW
insertAlert.setLong(10, serverVersionId); // NEW
insertAlert.execute();
} catch (SQLException ex) {
LogUtil.error("Failed to log alert", ex);
}
}
@Override
public synchronized int getLogCount(UUID uuid) {
try (Connection connection = dataSource.getConnection();
PreparedStatement countLogs = connection.prepareStatement(
"SELECT COUNT(*) FROM " + DatabaseConstants.VIOLATIONS_TABLE + " WHERE " + DatabaseConstants.VIOLATIONS_UUID_COLUMN + " = ?"
)
) {
countLogs.setObject(1, uuid);
ResultSet result = countLogs.executeQuery();
if (result.next()) {
return result.getInt(1);
}
} catch (SQLException ex) {
LogUtil.error("Failed to count logs", ex);
}
return 0;
}
@Override
public synchronized List<Violation> getViolations(UUID uuid, int page, int limit) {
try (Connection connection = dataSource.getConnection();
// Updated SELECT statement with all new joins and column selections
PreparedStatement fetchLogs = connection.prepareStatement(
"SELECT " +
"v." + DatabaseConstants.VIOLATIONS_ID_COLUMN + ", " +
"s." + DatabaseConstants.SERVERS_STRING_COLUMN + ", " +
"v." + DatabaseConstants.VIOLATIONS_UUID_COLUMN + ", " +
"cn." + DatabaseConstants.CHECK_NAMES_STRING_COLUMN + ", " +
"v." + quoteVerboseColumn() + ", " +
"v." + DatabaseConstants.VIOLATIONS_VL_COLUMN + ", " +
"v." + DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + ", " +
"gv." + DatabaseConstants.GRIM_VERSIONS_STRING_COLUMN + ", " + // NEW
"cb." + DatabaseConstants.CLIENT_BRANDS_STRING_COLUMN + ", " + // NEW
"clv." + DatabaseConstants.CLIENT_VERSIONS_STRING_COLUMN + ", " + // NEW
"srv." + DatabaseConstants.SERVER_VERSIONS_STRING_COLUMN + " " + // NEW
"FROM " + DatabaseConstants.VIOLATIONS_TABLE + " v " +
"JOIN " + DatabaseConstants.SERVERS_TABLE + " s ON v." + DatabaseConstants.VIOLATIONS_SERVER_ID_COLUMN + " = s.id " +
"JOIN " + DatabaseConstants.CHECK_NAMES_TABLE + " cn ON v." + DatabaseConstants.VIOLATIONS_CHECK_NAME_ID_COLUMN + " = cn.id " +
"JOIN " + DatabaseConstants.GRIM_VERSIONS_TABLE + " gv ON v." + DatabaseConstants.VIOLATIONS_GRIM_VERSION_ID_COLUMN + " = gv.id " + // NEW
"JOIN " + DatabaseConstants.CLIENT_BRANDS_TABLE + " cb ON v." + DatabaseConstants.VIOLATIONS_CLIENT_BRAND_ID_COLUMN + " = cb.id " + // NEW
"JOIN " + DatabaseConstants.CLIENT_VERSIONS_TABLE + " clv ON v." + DatabaseConstants.VIOLATIONS_CLIENT_VERSION_ID_COLUMN + " = clv.id " + // NEW
"JOIN " + DatabaseConstants.SERVER_VERSIONS_TABLE + " srv ON v." + DatabaseConstants.VIOLATIONS_SERVER_VERSION_ID_COLUMN + " = srv.id " + // NEW
"WHERE v." + DatabaseConstants.VIOLATIONS_UUID_COLUMN + " = ? ORDER BY v." + DatabaseConstants.VIOLATIONS_CREATED_AT_COLUMN + " DESC LIMIT ? OFFSET ?"
)
) {
fetchLogs.setObject(1, uuid);
fetchLogs.setInt(2, limit);
fetchLogs.setInt(3, Math.max(0, (page - 1)) * limit); // postgresql is not allowing negative numbers
return Violation.fromResultSet(fetchLogs.executeQuery());
} catch (SQLException ex) {
LogUtil.error("Failed to fetch logs", ex);
return null;
}
}
@Override
public void disconnect() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
public boolean sameConfig(String host, String db, String user, String pwd) {
String wantUrl = "jdbc:postgresql://" + host + "/" + db;
return wantUrl.equalsIgnoreCase(dataSource.getJdbcUrl())
&& user.equals(dataSource.getUsername())
&& pwd.equals(dataSource.getPassword());
}
}
@@ -39,7 +39,7 @@ 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.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;
@@ -120,13 +120,13 @@ public class GrimPlayer implements GrimUser {
public long lastTransSent = 0;
public long lastTransReceived = 0;
@Getter
private long playerClockAtLeast = System.nanoTime();
private long playerClockAtLeast;
public double lastWasClimbing = 0;
public boolean canSwimHop = false;
public int riptideSpinAttackTicks = 0;
public int powderSnowFrozenTicks = 0;
public boolean hasGravity = true;
public final long joinTime = System.currentTimeMillis();
public final long joinTime;
public boolean playerEntityHasGravity = true;
public VectorData predictedVelocity = new VectorData(new Vector3dm(), VectorData.VectorType.Normal);
public Vector3dm actualMovement = new Vector3dm();
@@ -272,6 +272,12 @@ public class GrimPlayer implements GrimUser {
public boolean lastJumping;
public GrimPlayer(@NotNull User user) {
this(user, System.currentTimeMillis(), System.nanoTime());
}
protected GrimPlayer(@NotNull User user, long joinTime, long clockStart) {
this.joinTime = joinTime;
this.playerClockAtLeast = clockStart;
this.user = user;
this.uuid = user.getUUID();
fireworks = new CompensatedFireworks(this); // Must be before checkmanager
@@ -364,15 +370,12 @@ public class GrimPlayer implements GrimUser {
// It's very difficult to test precedence so if there's issues with this bouncy implementation let me know
for (VectorData data : new HashSet<>(possibleMovements)) {
for (BlockFace direction : uncertaintyHandler.slimePistonBounces) {
final double x = data.vectorX;
final double y = data.vectorY;
final double z = data.vectorZ;
if (direction.getModX() != 0) {
possibleMovements.add(data.returnNewModified(direction.getModX(), y, z, VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(data.vector.clone().setX(direction.getModX()), VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModY() != 0) {
possibleMovements.add(data.returnNewModified(x, direction.getModY(), z, VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(data.vector.clone().setY(direction.getModY()), VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModZ() != 0) {
possibleMovements.add(data.returnNewModified(x, y, direction.getModZ(), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(data.vector.clone().setZ(direction.getModZ()), VectorData.VectorType.SlimePistonBounce));
}
}
}
@@ -400,7 +403,7 @@ public class GrimPlayer implements GrimUser {
// Transactions that we send don't count towards total limit
if (viaPacketTracker != null) viaPacketTracker.setIntervalPackets(viaPacketTracker.getIntervalPackets() - 1);
if (skipped > 0 && System.currentTimeMillis() - joinTime > 5000)
if (skipped > 0 && now() - joinTime > 5000)
checkManager.getCheck(TransactionOrder.class).flagAndAlert("skipped: " + skipped);
do {
@@ -409,14 +412,14 @@ public class GrimPlayer implements GrimUser {
break;
lastTransactionReceived.incrementAndGet();
lastTransReceived = System.currentTimeMillis();
transactionPing = (System.nanoTime() - data.second());
lastTransReceived = now();
transactionPing = (this.nano() - data.second());
playerClockAtLeast = data.second();
} while (data.first() != id);
// A transaction means a new tick, so handle any block interactions
CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedBreaks(this, false, 0, 0, System.currentTimeMillis());
CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, now());
CheckManagerListener.handleQueuedBreaks(this, false, 0, 0, now());
latencyUtils.handleNettySyncTransaction(lastTransactionReceived.get());
}
@@ -464,11 +467,11 @@ public class GrimPlayer implements GrimUser {
if (user.getEncoderState() != ConnectionState.PLAY) return;
// Send a packet once every 15 seconds to avoid any memory leaks
if (disableGrim && (System.nanoTime() - getPlayerClockAtLeast()) > 15e9) {
if (disableGrim && (this.nano() - getPlayerClockAtLeast()) > 15e9) {
return;
}
lastTransSent = System.currentTimeMillis();
lastTransSent = now();
short transactionID = (short) (-1 * (transactionIDCounter.getAndIncrement() & 0x7FFF));
try {
@@ -527,15 +530,20 @@ public class GrimPlayer implements GrimUser {
}
public void pollData() {
// Reset the debug timer for the Scheduler Thread.
// In Production: This is a no-op (0 cost).
// In Debug: This tells the clock "I am starting a new tick, don't freeze time."
this.processingPacket();
// Send a transaction at least once a tick, for timer and post check purposes
// Don't be the first to send the transaction, or we will stack overflow
//
// This will only really activate if there's no entities around the player being tracked
// 80 is a magic value that is roughly every other tick, we don't want to spam too many packets.
if (lastTransSent != 0 && lastTransSent + 80 < System.currentTimeMillis()) {
if (lastTransSent != 0 && lastTransSent + 80 < now()) {
sendTransaction(true); // send on netty thread
}
if ((System.nanoTime() - getPlayerClockAtLeast()) > maxTransactionTime * 1e9) {
if ((this.nano() - getPlayerClockAtLeast()) > maxTransactionTime * 1e9) {
timedOut();
}
@@ -799,10 +807,7 @@ public class GrimPlayer implements GrimUser {
}
public boolean isInWaterOrRain() {
return compensatedWorld.isRaining
|| Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f),
(block, x, y, z) -> Materials.isWater(CompensatedWorld.blockVersion, block)
);
return compensatedWorld.isRaining || Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f), (block) -> Materials.isWater(CompensatedWorld.blockVersion, block.first()));
}
@Contract(pure = true)
@@ -931,12 +936,12 @@ public class GrimPlayer implements GrimUser {
if (platformPlayer != null) platformPlayer.sendMessage(message);
}
public void resyncPosition(final int x, final int y, final int z) {
this.resyncHandler.resync(x, y, z, x, y, z);
public void resyncPosition(Vector3i pos) {
this.resyncHandler.resync(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ());
}
public void resyncPosition(final int x, final int y, final int z, final int sequenceID) {
this.resyncHandler.resyncPosition(x, y, z, sequenceID);
public void resyncPosition(Vector3i pos, int sequenceID) {
this.resyncHandler.resyncPosition(pos.x, pos.y, pos.z, sequenceID);
}
public void resyncPositions(SimpleCollisionBox box) {
@@ -994,4 +999,27 @@ public class GrimPlayer implements GrimUser {
return blockStateId;
}
/**
* Returns the current high-resolution time in nanoseconds.
* In Debug Mode, this will subtract time spent paused at breakpoints.
*/
public long nano() {
return System.nanoTime();
}
/**
* Returns the current wall-clock time in milliseconds.
* In Debug Mode, this will subtract time spent paused at breakpoints.
*/
public long now() {
return System.currentTimeMillis();
}
/**
* Called immediately when a packet starts processing.
* Used by DebugGrimPlayer to differentiate "Lag" (Pre-Packet) from "Breakpoints" (Intra-Packet).
* Optimized away (Dead Code Elimination) in production.
*/
public void processingPacket() {}
}
@@ -18,7 +18,6 @@ import ac.grim.grimac.predictionengine.predictions.rideable.PredictionEngineBoat
import ac.grim.grimac.utils.anticheat.update.PositionUpdate;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
@@ -71,13 +70,14 @@ 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().executeNonSimulatingForceResync();
}
}
long start = System.nanoTime();
long start = player.nano();
check(data);
long length = System.nanoTime() - start;
long length = player.nano() - start;
if (!player.disableGrim) {
predictionNanos = (predictionNanos * 499 / 500d) + (length / 500d);
@@ -231,7 +231,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
handleTeleport(update);
if (player.isClimbing) {
player.lastWasClimbing = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
Vector3dm ladder = player.clientVelocity.clone().setY(0.2);
PredictionEngineNormal.staticVectorEndOfTick(player, ladder);
player.lastWasClimbing = ladder.getY();
}
return;
}
@@ -350,7 +352,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
boolean clientClaimsRiptide = player.packetStateData.tryingToRiptide;
if (player.packetStateData.tryingToRiptide) {
long currentTime = System.currentTimeMillis();
long currentTime = player.now();
boolean isInWater = player.isInWaterOrRain();
if (currentTime - player.packetStateData.lastRiptide < 450 || !isInWater) {
@@ -361,7 +363,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
}
SimpleCollisionBox steppingOnBB = GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z).expand(player.getMovementThreshold()).offset(0, -1, 0);
Collisions.hasMaterial(player, steppingOnBB, (data) -> {
Collisions.hasMaterial(player, steppingOnBB, (pair) -> {
WrappedBlockState data = pair.first();
if (data.getType() == StateTypes.SLIME_BLOCK && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8)) {
player.uncertaintyHandler.isSteppingOnSlime = true;
player.uncertaintyHandler.isSteppingOnBouncyBlock = true;
@@ -407,8 +410,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.uncertaintyHandler.isNearGlitchyBlock = player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& Collisions.hasMaterial(player, expandedBB.copy().expand(0.2),
checkData -> BlockTags.ANVIL.contains(checkData.getType())
|| checkData.getType() == StateTypes.CHEST || checkData.getType() == StateTypes.TRAPPED_CHEST);
checkData -> BlockTags.ANVIL.contains(checkData.first().getType())
|| checkData.first().getType() == StateTypes.CHEST || checkData.first().getType() == StateTypes.TRAPPED_CHEST);
player.uncertaintyHandler.isOrWasNearGlitchyBlock = isGlitchy || player.uncertaintyHandler.isNearGlitchyBlock;
player.uncertaintyHandler.checkForHardCollision();
@@ -450,9 +453,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.clientVelocity = player.actualMovement.clone();
player.gravity = 0;
player.friction = 0.91f;
player.clientVelocity.setX(player.clientVelocity.getX() * player.friction);
player.clientVelocity.setY(PredictionEngineNormal.staticVectorEndOfTickY(player, player.clientVelocity.getY()));
player.clientVelocity.setZ(player.clientVelocity.getZ() * player.friction);
PredictionEngineNormal.staticVectorEndOfTick(player, player.clientVelocity);
} else if (riding == null) {
wasChecked = true;
@@ -521,7 +522,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
} // If it isn't any of these cases, the player is on a mob they can't control and therefore is exempt
// No, don't comment about the sqrt call. It doesn't matter unless you run sqrt thousands of times a second.
double offset = player.actualMovement.distance(player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
double offset = player.predictedVelocity.vector.distance(player.actualMovement);
offset = player.uncertaintyHandler.reduceOffset(offset);
if (player.packetStateData.tryingToRiptide != clientClaimsRiptide) {
@@ -534,19 +535,19 @@ public class MovementCheckRunner extends Check implements PositionCheck {
//
// Checking for oldClientVel being too high fixes BleachHack vertical scaffold
if (player.getSetbackTeleportUtil().getRequiredSetBack() != null && player.getSetbackTeleportUtil().getRequiredSetBack().getTicksComplete() == 1) {
SetBackData setback = player.getSetbackTeleportUtil().getRequiredSetBack();
Vector3dm setbackVel = player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity();
// A player must have velocity going INTO the ground to be able to jump
// Otherwise they could ignore upwards velocity that isn't useful into more useful upwards velocity (towering)
// So if they are supposed to be going upwards, or are supposed to be off the ground, resync
if (player.predictedVelocity.isJump()
&& !player.wasTouchingLava && !player.wasTouchingWater
&& ((setback.isHasVelocity() && setback.getVelocityY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
&& ((setbackVel != null && setbackVel.getY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
player.getSetbackTeleportUtil().executeForceResync();
}
boolean lavaBugFix = player.wasTouchingLava && player.predictedVelocity.isJump() &&
player.predictedVelocity.vectorY < 0.06 && player.predictedVelocity.vectorY > -0.02;
player.predictedVelocity.vector.getY() < 0.06 && player.predictedVelocity.vector.getY() > -0.02;
// Player ignored the knockback or is delaying it a tick... bad!
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().isHasVelocity()) {
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity() != null) {
// And then send it again!
player.getSetbackTeleportUtil().executeForceResync();
}
@@ -566,9 +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.setVectorX(lastKnownGoodPosition.getVectorX() * 0.6 * 0.91);
lastKnownGoodPosition.setVectorZ(lastKnownGoodPosition.getVectorZ() * 0.6 * 0.91);
lastKnownGoodPosition.setVector(lastKnownGoodPosition.getVector().multiply(0.6 * 0.91, 1, 0.6 * 0.91));
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
}
@@ -268,21 +268,22 @@ public class PointThreeEstimator {
isNearFluid = false;
// Check for flowing water
Collisions.hasMaterial(player, pointThreeBox, (state, x, y, z) -> {
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
final WrappedBlockState state = pair.first();
final StateType stateType = state.getType();
if (player.tagManager.block(SyncedTags.CLIMBABLE).contains(stateType) || (stateType == StateTypes.POWDER_SNOW && !player.inVehicle() && player.inventory.getBoots().getType() == ItemTypes.LEATHER_BOOTS)) {
isNearClimbable = true;
}
if (BlockTags.TRAPDOORS.contains(stateType)) {
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, x, y, z, state);
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, pair.second().getX(), pair.second().getY(), pair.second().getZ(), state);
}
if (stateType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
isNearBubbleColumn = true;
}
if (Materials.isWater(player.getClientVersion(), state) || state.getType() == StateTypes.LAVA) {
if (Materials.isWater(player.getClientVersion(), pair.first()) || pair.first().getType() == StateTypes.LAVA) {
isNearFluid = true;
}
@@ -364,7 +365,7 @@ public class PointThreeEstimator {
// If this tick is the tick after y velocity was by 0, a stepping movement is POSSIBLE to have been hidden
// A bit hacky... is there a better way? I'm unsure...
boolean likelyStepSkip = player.isPointThree() && (data.vectorY > -0.08 && data.vectorY < 0.06) && couldStep;
boolean likelyStepSkip = player.isPointThree() && (data.vector.getY() > -0.08 && data.vector.getY() < 0.06) && couldStep;
// We need to do hypot calculations for all 3 axis
// sqrt(sqrt(x^2 + z^2)^2 + y^2) = hypot(x, z, y)
@@ -410,7 +411,7 @@ public class PointThreeEstimator {
wasAlwaysCertain = false;
// Head hitters return the vector to 0, and then apply gravity to it.
// Not much room for abuse for this, so keep it lenient
return -Math.max(0, vector.vectorY) - 0.1 - fluidAddition;
return -Math.max(0, vector.vector.getY()) - 0.1 - fluidAddition;
} else if (player.uncertaintyHandler.wasAffectedByStuckSpeed()) {
wasAlwaysCertain = false;
// This shouldn't be needed but stuck speed can desync very easily with 0.03...
@@ -424,7 +425,7 @@ public class PointThreeEstimator {
double minMovement = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.003 : 0.005;
// This should likely be refactored, but it works well.
double yVel = vector.vectorY;
double yVel = vector.vector.getY();
double maxYTraveled = 0;
boolean first = true;
do {
@@ -451,7 +452,7 @@ public class PointThreeEstimator {
// We aren't making progress, avoid infinite loop (This can be due to the player not having gravity)
if (yVel == 0) break;
} while (Math.abs(maxYTraveled + vector.vectorY) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
} while (Math.abs(maxYTraveled + vector.vector.getY()) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
if (maxYTraveled != 0) {
wasAlwaysCertain = false;
@@ -66,13 +66,13 @@ public class SneakingEstimator extends Check implements PostPredictionCheck {
if (data.isJump() == player.predictedVelocity.isJump() && data.isKnockback() == player.predictedVelocity.isKnockback() && data.isExplosion() == player.predictedVelocity.isExplosion()) {
// Fuck, we are compounding this which is very dangerous. After light testing seems fine... can we do better?
if (player.uncertaintyHandler.lastStuckWest.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckNorth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vectorX);
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vectorZ);
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vector.getX());
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vector.getZ());
}
if (player.uncertaintyHandler.lastStuckEast.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckSouth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vectorX);
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vectorZ);
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vector.getX());
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vector.getZ());
}
}
}
@@ -264,7 +264,7 @@ public class UncertaintyHandler {
return 0.06;
// Not worth my time to fix this because checking flying generally sucks - if player was flying in last 2 ticks
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vectorY) < (4.5 * player.flySpeed - 0.25))
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vector.getY()) < (4.5 * player.flySpeed - 0.25))
return 0.06;
double pointThree = player.getMovementThreshold();
@@ -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;
@@ -89,12 +89,12 @@ public class MovementTicker {
player.uncertaintyHandler.collidingEntities.add(possibleCollidingEntities);
}
private boolean isHorizontalCollisionSoft(double x, double y, double z) {
double horizontalLengthSquared = x * x + z * z;
private boolean isHorizontalCollisionSoft(Vector3dm collide) {
double horizontalLengthSquared = collide.getX() * collide.getX() + collide.getZ() * collide.getZ();
if (horizontalLengthSquared < 1E-5F) return false;
float xxa = (float) player.predictedVelocity.input.x();
float zza = (float) player.predictedVelocity.input.z();
float xxa = (float) player.predictedVelocity.input.getX();
float zza = (float) player.predictedVelocity.input.getZ();
float yawInRadians = player.yaw * (float) (Math.PI / 180.0);
double sin = player.trigHandler.sin(yawInRadians);
@@ -102,17 +102,17 @@ public class MovementTicker {
double g = xxa * cos - zza * sin;
double h = zza * cos + xxa * sin;
double i = g * g + h * h;
return i >= 1E-5F && Math.acos((g * x + h * z) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
return i >= 1E-5F && Math.acos((g * collide.getX() + h * collide.getZ()) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
}
public void move(Vector3dm inputVel, double x, double y, double z) {
public void move(Vector3dm inputVel, Vector3dm collide) {
if (player.stuckSpeedMultiplier.getX() < 0.99) {
player.clientVelocity = new Vector3dm();
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2)) {
boolean xAxis = !GrimMath.equal(inputVel.getX(), x);
boolean zAxis = !GrimMath.equal(inputVel.getZ(), z);
boolean xAxis = !GrimMath.equal(inputVel.getX(), collide.getX());
boolean zAxis = !GrimMath.equal(inputVel.getZ(), collide.getZ());
if (xAxis) {
player.clientVelocity.setX(0);
@@ -123,20 +123,20 @@ public class MovementTicker {
}
player.horizontalCollision = xAxis || zAxis;
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(x, y, z);
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(collide);
} else {
if (inputVel.getX() != x) {
if (inputVel.getX() != collide.getX()) {
player.clientVelocity.setX(0);
}
if (inputVel.getZ() != z) {
if (inputVel.getZ() != collide.getZ()) {
player.clientVelocity.setZ(0);
}
player.horizontalCollision = inputVel.getX() != x || inputVel.getZ() != z;
player.horizontalCollision = inputVel.getX() != collide.getX() || inputVel.getZ() != collide.getZ();
}
player.verticalCollision = inputVel.getY() != y;
player.verticalCollision = inputVel.getY() != collide.getY();
// Avoid order of collisions being wrong because 0.03 movements
// Stepping movement USUALLY means the vehicle in on the ground as vehicles can't jump
@@ -145,7 +145,7 @@ public class MovementTicker {
boolean calculatedOnGround = (player.verticalCollision && inputVel.getY() < 0.0D);
// If the player is on the ground with a y velocity of 0, let the player decide (too close to call)
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && y > -SimpleCollisionBox.COLLISION_EPSILON && y <= 0 && !player.inVehicle())
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() > -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() <= 0 && !player.inVehicle())
calculatedOnGround = player.onGround;
player.clientClaimsLastOnGround = player.onGround;
@@ -177,9 +177,9 @@ public class MovementTicker {
if (player.onGround) {
player.fallDistance = 0;
} else if (y < 0) {
player.fallDistance = (player.fallDistance) - y;
player.vehicleData.lastYd = y;
} else if (collide.getY() < 0) {
player.fallDistance = (player.fallDistance) - collide.getY();
player.vehicleData.lastYd = collide.getY();
}
// Striders call the method for inside blocks AGAIN!
@@ -187,11 +187,11 @@ public class MovementTicker {
Collisions.handleInsideBlocks(player);
}
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(x, y, z), player.boundingBox, player.onGround);
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(collide.getX(), collide.getY(), collide.getZ()), player.boundingBox, player.onGround);
StateType onBlock = BlockProperties.getOnPos(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
// Hack with 1.14+ poses issue
if (inputVel.getY() != y) {
if (inputVel.getY() != collide.getY()) {
// If the client supports slime blocks
// And the block is a slime block
// Or the block is honey and was replaced by viaversion
@@ -214,15 +214,14 @@ public class MovementTicker {
player.clientVelocity.setY(0);
}
}
// TODO reimplement? the comment about reimplementing clampMovementToHardBorder is 3 years old...
// collide = PredictionEngine.clampMovementToHardBorder(player, collide);
collide = PredictionEngine.clampMovementToHardBorder(player, collide);
// The game disregards movements smaller than 1e-7 (such as in boats)
double collideLengthSquared = GrimMath.lengthSquared(x, y, z);
if (collideLengthSquared <= 1e-7
if (collide.lengthSquared() <= 1e-7
// New condition added in 1.21.2
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collideLengthSquared >= 1e-7)) {
x = y = z = 0;
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collide.lengthSquared() >= 1e-7)) {
collide = new Vector3dm();
} else if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
Vector3d from = new Vector3d(player.lastX, player.lastY, player.lastZ);
Vector3d to = new Vector3d(player.x, player.y, player.z);
@@ -231,7 +230,7 @@ public class MovementTicker {
}
// This is where vanilla moves the bounding box and sets it
player.predictedVelocity = new VectorData(x, y, z, player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
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(f, 1, f);
@@ -307,10 +306,10 @@ public class MovementTicker {
// Meaning we should scan upwards!
oldBB.expand(-SimpleCollisionBox.COLLISION_EPSILON);
double posX = Math.max(0, player.predictedVelocity.vectorX) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vectorX) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vectorZ) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vectorZ) - SimpleCollisionBox.COLLISION_EPSILON;
double posX = Math.max(0, player.predictedVelocity.vector.getX()) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vector.getX()) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vector.getZ()) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vector.getZ()) - SimpleCollisionBox.COLLISION_EPSILON;
boolean xAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(negX, 0, 0).expandMax(posX, 0, 0));
boolean zAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(0, 0, negZ).expandMax(0, 0, posZ));
@@ -436,7 +435,7 @@ public class MovementTicker {
// 1.13 and below players can't climb ladders while touching water
// yes, 1.13 players cannot climb ladders underwater
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) && player.isClimbing) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
}
} else {
@@ -448,7 +447,7 @@ public class MovementTicker {
// Lava movement changed in 1.16
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16) && player.slightlyTouchingLava) {
player.clientVelocity = player.clientVelocity.multiply(0.5D, 0.800000011920929D, 0.5D);
player.clientVelocity.setY(FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, player.clientVelocity.getY()));
player.clientVelocity = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity);
} else {
player.clientVelocity.multiply(0.5D);
}
@@ -9,7 +9,6 @@ import ac.grim.grimac.utils.data.KnownInput;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -32,22 +31,22 @@ public class PredictionEngine {
return outputVel;
}
public static Vec2d transformInputsToVector(GrimPlayer player, int strafe, int forward) {
public static Vector3dm transformInputsToVector(GrimPlayer player, Vector3dm theoreticalInput) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
Vec2 moveVector = new Vec2((float) strafe, (float) forward).normalized();
Vec2 moveVector = new Vec2((float) theoreticalInput.getX(), (float) theoreticalInput.getZ()).normalized();
Vec2 input = modifyInput(player, moveVector);
return new Vec2d(input.x(), input.y());
return new Vector3dm(input.x(), 0, input.y());
}
float bestPossibleX;
float bestPossibleZ;
// Slow movement was determined by the previous pose
if (player.isSlowMovement) {
bestPossibleX = (float) (strafe * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (forward * player.sneakingSpeedMultiplier);
bestPossibleX = (float) (theoreticalInput.getX() * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (theoreticalInput.getZ() * player.sneakingSpeedMultiplier);
} else {
bestPossibleX = Math.min(Math.max(-1f, strafe), 1f);
bestPossibleZ = Math.min(Math.max(-1f, forward), 1f);
bestPossibleX = Math.min(Math.max(-1f, Math.round(theoreticalInput.getX())), 1f);
bestPossibleZ = Math.min(Math.max(-1f, Math.round(theoreticalInput.getZ())), 1f);
}
if (player.packetStateData.isSlowedByUsingItem()) {
@@ -55,23 +54,18 @@ public class PredictionEngine {
bestPossibleZ *= 0.2F;
}
double inputVectorX = bestPossibleX;
double inputVectorZ = bestPossibleZ;
inputVectorX *= 0.98F;
inputVectorZ *= 0.98F;
Vector3dm inputVector = new Vector3dm(bestPossibleX, 0, bestPossibleZ);
inputVector.multiply(0.98F);
// Simulate float rounding imprecision
inputVectorX = (float) inputVectorX;
inputVectorZ = (float) inputVectorZ;
inputVector = new Vector3dm((float) inputVector.getX(), (float) inputVector.getY(), (float) inputVector.getZ());
double lengthSquared = GrimMath.lengthSquared(inputVectorX, inputVectorZ);
if (lengthSquared > 1) {
double d0 = Math.sqrt(lengthSquared);
inputVectorX = inputVectorX / d0;
inputVectorZ = inputVectorZ / d0;
if (inputVector.lengthSquared() > 1) {
double d0 = Math.sqrt(inputVector.getX() * inputVector.getX() + inputVector.getY() * inputVector.getY() + inputVector.getZ() * inputVector.getZ());
inputVector = new Vector3dm(inputVector.getX() / d0, inputVector.getY() / d0, inputVector.getZ() / d0);
}
return new Vec2d(inputVectorX, inputVectorZ);
return inputVector;
}
public static Vec2 modifyInput(GrimPlayer player, Vec2 moveVector) {
@@ -146,7 +140,7 @@ public class PredictionEngine {
// Client velocity - before collision and carried into the next tick
// Predicted velocity - after collision and not carried into the next tick
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vector);
endOfTick(player, player.gravity);
}
@@ -240,7 +234,7 @@ public class PredictionEngine {
beforeCollisionMovement = primaryPushMovement;
// We basically want to avoid falsing ground spoof, try to find a vector that works
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vectorY))
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vector.getY()))
resultAccuracy += 0.0001 * 0.0001;
bestInput = resultAccuracy;
@@ -265,7 +259,7 @@ public class PredictionEngine {
}
private Pair<Vector3dm, Vector3dm> doSeekingWallCollisions(GrimPlayer player, Vector3dm primaryPushMovement, Vector3dm originalClientVel, VectorData clientVelAfterInput) {
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vectorY == 0;
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vector.getY() == 0;
// Extra collision epsilon required for vehicles to be accurate
double xAdditional = Math.signum(primaryPushMovement.getX()) * SimpleCollisionBox.COLLISION_EPSILON;
// The server likes sending y=0 kb "lifting" the player off the ground.
@@ -335,9 +329,10 @@ public class PredictionEngine {
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
// Calculate the Y velocity after friction
double y = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
Vector3dm hackyClimbVector = new Vector3dm(0, 0.2, 0);
PredictionEngineNormal.staticVectorEndOfTick(player, hackyClimbVector);
pointThreePossibilities.add(new VectorData(0, y, 0, VectorData.VectorType.ZeroPointZeroThree));
pointThreePossibilities.add(new VectorData(hackyClimbVector, VectorData.VectorType.ZeroPointZeroThree));
}
// This is a secure method to add jumping vectors to this list
@@ -362,24 +357,19 @@ public class PredictionEngine {
for (VectorData vectorData : data) {
// Sneaking in water
if (vectorData.isKnockback() && player.baseTickAddition.lengthSquared() != 0) {
vectorData.vectorX += player.baseTickAddition.getX();
vectorData.vectorY += player.baseTickAddition.getY();
vectorData.vectorZ += player.baseTickAddition.getZ();
vectorData.vector = vectorData.vector.add(player.baseTickAddition);
}
// Water pushing movement is affected by initial velocity due to 0.003 eating pushing in the past
if (vectorData.isKnockback() && player.baseTickWaterPushing.lengthSquared() != 0) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
Vector3dm vec3 = player.baseTickWaterPushing.clone();
if (Math.abs(vectorData.vectorX) < 0.003 && Math.abs(vectorData.vectorZ) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
if (Math.abs(vectorData.vector.getX()) < 0.003 && Math.abs(vectorData.vector.getZ()) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
vec3 = vec3.normalize().multiply(0.0045000000000000005);
}
vectorData.vectorX += vec3.getX();
vectorData.vectorY += vec3.getY();
vectorData.vectorZ += vec3.getZ();
vectorData.vector = vectorData.vector.add(vec3);
} else {
vectorData.vectorX += player.baseTickWaterPushing.getX();
vectorData.vectorY += player.baseTickWaterPushing.getY();
vectorData.vectorZ += player.baseTickWaterPushing.getZ();
vectorData.vector = vectorData.vector.add(player.baseTickWaterPushing);
}
}
}
@@ -415,9 +405,7 @@ public class PredictionEngine {
if (!player.inVehicle() || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) return;
for (VectorData vectorData : data) {
vectorData.vectorX = vectorData.vectorX * 0.98;
vectorData.vectorY = vectorData.vectorY * 0.98;
vectorData.vectorZ = vectorData.vectorZ * 0.98;
vectorData.vector = vectorData.vector.clone().multiply(0.98);
}
}
@@ -425,11 +413,11 @@ public class PredictionEngine {
for (int x = 1; x <= Math.min(player.maxAttackSlow, 5); x++) {
for (VectorData data : new HashSet<>(velocities)) {
if (player.minAttackSlow > 0) {
data.vectorX = data.vectorX * 0.6;
data.vectorZ = data.vectorZ * 0.6;
data.vector.setX(data.vector.getX() * 0.6);
data.vector.setZ(data.vector.getZ() * 0.6);
data.addVectorType(VectorData.VectorType.AttackSlow);
} else {
velocities.add(data.returnNewModified(data.vectorX * 0.6, data.vectorY, data.vectorZ * 0.6, VectorData.VectorType.AttackSlow));
velocities.add(data.returnNewModified(data.vector.clone().multiply(0.6, 1, 0.6), VectorData.VectorType.AttackSlow));
}
}
@@ -454,56 +442,45 @@ public class PredictionEngine {
Set<VectorData> vectors = stupidVectors && stuckOnEdge ? new HashSet<>(velocities) : velocities;
for (VectorData vector : vectors) {
if (stupidVectors) {
if (Collisions.getHorizontalDistanceSqr(vector.vectorX, vector.vectorZ) < 9.0E-6) {
if (Collisions.getHorizontalDistanceSqr(vector.vector) < 9.0E-6) {
if (stuckOnEdge) {
VectorData edgeVector = vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, vector.vectorType);
if (Math.abs(edgeVector.vectorY) < minimumMovement) {
edgeVector.setY(0D);
VectorData edgeVector = vector.returnNewModified(vector.vector.clone(), vector.vectorType);
if (Math.abs(edgeVector.vector.getY()) < minimumMovement) {
edgeVector.vector.setY(0D);
}
velocities.add(edgeVector);
}
vector.setX(0D);
vector.setZ(0D);
vector.vector.setX(0D);
vector.vector.setZ(0D);
}
} else {
if (Math.abs(vector.vectorX) < minimumMovement) {
vector.setX(0D);
if (Math.abs(vector.vector.getX()) < minimumMovement) {
vector.vector.setX(0D);
}
if (Math.abs(vector.vectorZ) < minimumMovement) {
vector.setZ(0D);
if (Math.abs(vector.vector.getZ()) < minimumMovement) {
vector.vector.setZ(0D);
}
}
if (Math.abs(vector.vectorY) < minimumMovement) {
vector.setY(0D);
if (Math.abs(vector.vector.getY()) < minimumMovement) {
vector.vector.setY(0D);
}
}
}
public void addExplosionToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
if (player.likelyExplosions == null && player.firstBreadExplosion == null) return;
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
if (player.likelyExplosions != null) {
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
existingVelocities.add(new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion));
existingVelocities.add(new VectorData(vector.vector.clone().add(player.likelyExplosions.vector), vector, VectorData.VectorType.Explosion));
}
if (player.firstBreadExplosion != null) {
// Previously we called Vector3dm.add(player.likelyExplosions.vector) after checking (player.firstBreadExplosion != null)
// which would've thrown an NPE if player.likelyExplosions could be null here, we can ignore this
// empirically this means when firstBreadExplosion != null then likelyExplosions is also != null
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
VectorData intermediateData = new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion);
VectorData finalData = new VectorData(newX, newY, newZ, intermediateData, VectorData.VectorType.FirstBreadExplosion);
existingVelocities.add(finalData);
existingVelocities.add(new VectorData(vector.vector.clone().add(player.firstBreadExplosion.vector), vector, VectorData.VectorType.Explosion)
.returnNewModified(vector.vector.clone().add(player.firstBreadExplosion.vector), VectorData.VectorType.FirstBreadExplosion));
}
}
}
@@ -556,18 +533,16 @@ public class PredictionEngine {
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.vectorY >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
aScore += 2;
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vectorY >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
bScore += 2;
if (aScore != bScore)
return Integer.compare(aScore, bScore);
return Double.compare(
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), a.vectorX, a.vectorY, a.vectorZ),
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), b.vectorX, b.vectorY, b.vectorZ));
return Double.compare(a.vector.distanceSquared(player.actualMovement), b.vector.distanceSquared(player.actualMovement));
}
public Vector3dm handleStartingVelocityUncertainty(GrimPlayer player, VectorData vector, Vector3dm targetVec) {
@@ -621,81 +596,69 @@ public class PredictionEngine {
// 0.075 seems safe?
//
// Be somewhat careful as there is an antikb (for horizontal) that relies on this lenience
final double uncertaintyX = avgColliding * 0.08;
final double uncertaintyY = additionVertical;
final double uncertaintyZ = avgColliding * 0.08;
Vector3dm uncertainty = new Vector3dm(avgColliding * 0.08, additionVertical, avgColliding * 0.08);
final double minUncertaintyX = player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal;
final double minUncertaintyY = player.uncertaintyHandler.yNegativeUncertainty - bonusY;
final double minUncertaintyZ = player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal;
Vector3dm min = new Vector3dm(player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal, -bonusY + player.uncertaintyHandler.yNegativeUncertainty, player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal);
Vector3dm max = new Vector3dm(player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal, bonusY + player.uncertaintyHandler.yPositiveUncertainty, player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal);
final double maxUncertaintyX = player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal;
final double maxUncertaintyY = player.uncertaintyHandler.yPositiveUncertainty + bonusY;
final double maxUncertaintyZ = player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal;
double minVecX = vector.vectorX + (minUncertaintyX - uncertaintyX);
double minVecY = vector.vectorY + (minUncertaintyY - uncertaintyY);
double minVecZ = vector.vectorZ + (minUncertaintyZ - uncertaintyZ);
double maxVecX = vector.vectorX + (maxUncertaintyX + uncertaintyX);
double maxVecY = vector.vectorY + (maxUncertaintyY + uncertaintyY);
double maxVecZ = vector.vectorZ + (maxUncertaintyZ + uncertaintyZ);
Vector3dm minVector = vector.vector.clone().add(min.subtract(uncertainty));
Vector3dm maxVector = vector.vector.clone().add(max.add(uncertainty));
// Handle the player landing within 0.03 movement, which resets Y velocity
if (player.uncertaintyHandler.onGroundUncertain && vector.vectorY < 0) {
maxVecY = 0;
if (player.uncertaintyHandler.onGroundUncertain && vector.vector.getY() < 0) {
maxVector.setY(0);
}
// Handles stuff like missing idle packet causing gravity to be missed (plus 0.03 of course)
double gravityOffset = player.pointThreeEstimator.getAdditionalVerticalUncertainty(vector);
if (gravityOffset > 0) {
maxVecY += gravityOffset;
maxVector.setY(maxVector.getY() + gravityOffset);
} else {
minVecY += gravityOffset;
minVector.setY(minVector.getY() + gravityOffset);
}
// Handle vertical fluid pushing within 0.03
double verticalFluid = player.pointThreeEstimator.getVerticalFluidPushingUncertainty(vector);
minVecY -= verticalFluid;
minVector.setY(minVector.getY() - verticalFluid);
// Handle vertical bubble column stupidity within 0.03
double bubbleFluid = player.pointThreeEstimator.getVerticalBubbleUncertainty(vector);
maxVecY += bubbleFluid;
minVecY -= bubbleFluid;
maxVector.setY(maxVector.getY() + bubbleFluid);
minVector.setY(minVector.getY() - bubbleFluid);
// We can't simulate the player's Y velocity, unknown number of ticks with a gravity change
// Feel free to simulate all 104857600000000000000000000 possibilities!
if (!player.pointThreeEstimator.canPredictNextVerticalMovement()) {
minVecY -= player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY);
minVector.setY(minVector.getY() - player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY));
}
// Hidden slime block bounces by missing idle tick and 0.03
if (player.actualMovement.getY() >= 0 && player.uncertaintyHandler.influencedByBouncyBlock()) {
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty != 0 && !vector.isJump()) { // jumping overrides slime block
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVecY) {
maxVecY = player.uncertaintyHandler.thisTickSlimeBlockUncertainty;
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVector.getY()) {
maxVector.setY(player.uncertaintyHandler.thisTickSlimeBlockUncertainty);
}
if (minVecY > 0) minVecY = 0;
if (minVector.getY() > 0) minVector.setY(0);
}
}
if (vector.isZeroPointZeroThree() && vector.isSwimHop()) {
minVecY -= 0.06; // Fluid pushing downwards hidden by 0.03
minVector.setY(minVector.getY() - 0.06); // Fluid pushing downwards hidden by 0.03
}
SimpleCollisionBox box = new SimpleCollisionBox(minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
SimpleCollisionBox box = new SimpleCollisionBox(minVector, maxVector);
box.sort();
// https://github.com/MWHunter/Grim/issues/398
// Thank mojang for removing the idle packet resulting in this hacky mess
double levitation = player.pointThreeEstimator.positiveLevitation(maxVecY);
double levitation = player.pointThreeEstimator.positiveLevitation(maxVector.getY());
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.positiveLevitation(minVecY);
levitation = player.pointThreeEstimator.positiveLevitation(minVector.getY());
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(maxVecY);
levitation = player.pointThreeEstimator.negativeLevitation(maxVector.getY());
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(minVecY);
levitation = player.pointThreeEstimator.negativeLevitation(minVector.getY());
box.combineToMinimum(box.minX, levitation, box.minZ);
@@ -706,12 +669,12 @@ public class PredictionEngine {
box.maxZ += sneaking.getSneakingPotentialHiddenVelocity().maxZ;
if (player.uncertaintyHandler.fireworksBox != null) {
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vectorX);
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vectorY);
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vectorZ);
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vectorX);
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vectorY);
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vectorZ);
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vector.getX());
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vector.getY());
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vector.getZ());
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vector.getX());
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vector.getY());
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vector.getZ());
box.expandMin(minXdiff, minYdiff, minZdiff);
box.expandMax(maxXdiff, maxYdiff, maxZdiff);
@@ -764,7 +727,7 @@ public class PredictionEngine {
//
// Or the player is switching in and out of controlling a vehicle, in which friction messes it up
//
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vectorY > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vectorY + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vector.getY() > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vector.getY() + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
box.expandToAbsoluteCoordinates(0, 0, 0);
}
@@ -790,26 +753,22 @@ public class PredictionEngine {
box.expand(0.001); // Ignore 1e-3 offsets as we don't know starting vel
}
minVecX = box.minX;
minVecY = box.minY;
minVecZ = box.minZ;
maxVecX = box.maxX;
maxVecY = box.maxY;
maxVecZ = box.maxZ;
minVector = box.min();
maxVector = box.max();
if (pistonX != 0) {
minVecX = Math.min(minVecX - pistonX, pistonX);
maxVecX = Math.max(maxVecX + pistonX, pistonX);
minVector.setX(Math.min(minVector.getX() - pistonX, pistonX));
maxVector.setX(Math.max(maxVector.getX() + pistonX, pistonX));
}
if (pistonY != 0) {
minVecY = Math.min(minVecY - pistonY, pistonY);
maxVecY = Math.max(maxVecY + pistonY, pistonY);
minVector.setY(Math.min(minVector.getY() - pistonY, pistonY));
maxVector.setY(Math.max(maxVector.getY() + pistonY, pistonY));
}
if (pistonZ != 0) {
minVecZ = Math.min(minVecZ - pistonZ, pistonZ);
maxVecZ = Math.max(maxVecZ + pistonZ, pistonZ);
minVector.setZ(Math.min(minVector.getZ() - pistonZ, pistonZ));
maxVector.setZ(Math.max(maxVector.getZ() + pistonZ, pistonZ));
}
return VectorUtils.cutBoxToVector(targetVec, minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
return VectorUtils.cutBoxToVector(targetVec, minVector, maxVector);
}
public void endOfTick(GrimPlayer player, double d) {
@@ -869,20 +828,15 @@ public class PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
Vec2d input = transformInputsToVector(player, strafe, forward);
Vector3dm movementResultFromInput = getMovementResultFromInput(player, input.x(), 0, input.z(), speed, player.yaw);
VectorData result = new VectorData(possibleLastTickOutput.vectorX + movementResultFromInput.getX(),
possibleLastTickOutput.vectorY + movementResultFromInput.getY(),
possibleLastTickOutput.vectorZ + movementResultFromInput.getZ(),
Vector3dm input = transformInputsToVector(player, new Vector3dm(strafe, 0, forward));
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
.add(getMovementResultFromInput(player, input, speed, player.yaw)),
possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = input;
if (applyStuckSpeed != 0) {
result = result.returnNewModified(result.vectorX * player.stuckSpeedMultiplier.getX(),
result.vectorY * player.stuckSpeedMultiplier.getY(),
result.vectorZ * player.stuckSpeedMultiplier.getZ(),
VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
}
result = result.returnNewModified(handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
// Signal that we need to flip sneaking bounding box
if (loopUsingItem == 1)
result = result.returnNewModified(VectorData.VectorType.Flip_Use_Item);
@@ -948,23 +902,25 @@ public class PredictionEngine {
// This is just the vanilla equation, which accepts invalid inputs greater than 1
// We need it because of collision support when a player is using speed
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float speed, float yaw) {
float f2InRadians = GrimMath.radians(yaw);
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float f2InRadians = GrimMath.radians(f2);
float f3 = player.trigHandler.sin(f2InRadians);
float f4 = player.trigHandler.cos(f2InRadians);
double xResult = x * f4 - z * f3;
double zResult = z * f4 + x * f3;
double xResult = inputVector.getX() * f4 - inputVector.getZ() * f3;
double zResult = inputVector.getZ() * f4 + inputVector.getX() * f3;
return new Vector3dm(xResult * speed, 0, zResult * speed);
return new Vector3dm(xResult * f, 0, zResult * f);
}
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
return new Vector3dm(x, y, z);
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
return vector;
}
public void doJump(GrimPlayer player, double[] vectorArr) {
if (!player.lastOnGround || player.onGround) return;
JumpPower.jumpFromGround(player, vectorArr);
public void doJump(GrimPlayer player, Vector3dm vector) {
if (!player.lastOnGround || player.onGround)
return;
JumpPower.jumpFromGround(player, vector);
}
}
@@ -3,7 +3,6 @@ package ac.grim.grimac.predictionengine.predictions;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
@@ -14,13 +13,11 @@ import java.util.List;
import java.util.Set;
public class PredictionEngineElytra extends PredictionEngine {
public static void applyElytraMovementInPlace(GrimPlayer player,
double[] vectorArr,
double lookX, double lookY, double lookZ) {
public static Vector3dm getElytraMovement(GrimPlayer player, Vector3dm vector, Vector3dm lookVector) {
float pitchRadians = GrimMath.radians(player.pitch);
double horizontalSqrt = Math.sqrt(lookX * lookX + lookZ * lookZ);
double horizontalLength = GrimMath.length(vectorArr[0], 0, vectorArr[2]);
double length = GrimMath.length(lookX, lookY, lookZ);
double horizontalSqrt = Math.sqrt(lookVector.getX() * lookVector.getX() + lookVector.getZ() * lookVector.getZ());
double horizontalLength = vector.clone().setY(0).length();
double length = lookVector.length();
// Mojang changed from using their math to using regular java math in 1.18.2 elytra movement
double vertCosRotation = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2) ? Math.cos(pitchRadians) : player.trigHandler.cos(pitchRadians);
@@ -34,30 +31,27 @@ public class PredictionEngineElytra extends PredictionEngine {
recalculatedGravity = player.getClientVersion().isOlderThan(ClientVersion.V_1_20_5) ? 0.01 : Math.min(recalculatedGravity, 0.01);
}
vectorArr[1] += recalculatedGravity * (-1.0D + vertCosRotation * 0.75D);
vector.add(0.0D, recalculatedGravity * (-1.0D + vertCosRotation * 0.75D), 0.0D);
double d5;
// Handle slowing the player down when falling
if (vectorArr[1] < 0.0D && horizontalSqrt > 0.0D) {
d5 = vectorArr[1] * -0.1D * vertCosRotation;
vectorArr[0] += lookX * d5 / horizontalSqrt;
vectorArr[1] += d5;
vectorArr[2] += lookZ * d5 / horizontalSqrt;
if (vector.getY() < 0.0D && horizontalSqrt > 0.0D) {
d5 = vector.getY() * -0.1D * vertCosRotation;
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;
vectorArr[0] += -lookX * d5 / horizontalSqrt;
vectorArr[1] += d5 * 3.2D;
vectorArr[2] += -lookZ * d5 / horizontalSqrt;
vector.add(-lookVector.getX() * d5 / horizontalSqrt, d5 * 3.2D, -lookVector.getZ() * d5 / horizontalSqrt);
}
// Handle accelerating the player sideways
if (horizontalSqrt > 0) {
vectorArr[0] += (lookX / horizontalSqrt * horizontalLength - vectorArr[0]) * 0.1D;
vectorArr[2] += (lookZ / horizontalSqrt * horizontalLength - vectorArr[2]) * 0.1D;
vector.add((lookVector.getX() / horizontalSqrt * horizontalLength - vector.getX()) * 0.1D, 0.0D, (lookVector.getZ() / horizontalSqrt * horizontalLength - vector.getZ()) * 0.1D);
}
return vector;
}
// Inputs have no effect on movement
@@ -71,19 +65,11 @@ public class PredictionEngineElytra extends PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
for (VectorData data : possibleVectors) {
double[] vector = {data.vectorX, data.vectorY, data.vectorZ};
applyElytraMovementInPlace(player, vector, currentLook.getX(), currentLook.getY(), currentLook.getZ());
if (applyStuckSpeed != 0) {
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
vector[0] *= 0.99F;
vector[1] *= 0.98F;
vector[2] *= 0.99f;
VectorData modified = data.returnNewModified(vector[0], vector[1], vector[2], VectorData.VectorType.InputResult);
modified.input = new Vec2d(0, 0);
Vector3dm elytraResult = getElytraMovement(player, data.vector.clone(), currentLook);
if (applyStuckSpeed != 0) elytraResult.multiply(player.stuckSpeedMultiplier);
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);
}
}
@@ -11,23 +11,18 @@ import java.util.Set;
public class PredictionEngineLava extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
for (VectorData vector : new HashSet<>(existingVelocities)) {
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
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(startX, startY + 0.04f, startZ, 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) {
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
}
}
}
@@ -19,24 +19,20 @@ import java.util.Set;
public class PredictionEngineNormal extends PredictionEngine {
public static double staticVectorEndOfTickY(GrimPlayer player, double y) {
double adjustedY = y;
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector) {
double adjustedY = vector.getY();
final OptionalInt levitation = player.compensatedEntities.getPotionLevelForPlayer(PotionTypes.LEVITATION);
if (levitation.isPresent()) {
adjustedY += (0.05 * (levitation.getAsInt() + 1) - y) * 0.2;
adjustedY += (0.05 * (levitation.getAsInt() + 1) - vector.getY()) * 0.2;
// Reset fall distance with levitation
player.fallDistance = 0;
} else if (player.hasGravity) {
adjustedY -= player.gravity;
}
return adjustedY * 0.98F;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData) {
vectorData.vectorX = vectorData.vectorX * player.friction;
vectorData.vectorY = staticVectorEndOfTickY(player, vectorData.vectorY);
vectorData.vectorZ = vectorData.vectorZ * player.friction;
vector.setX(vector.getX() * player.friction);
vector.setY(adjustedY * 0.98F);
vector.setZ(vector.getZ() * player.friction);
}
@Override
@@ -45,9 +41,8 @@ public class PredictionEngineNormal extends PredictionEngine {
return;
}
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
double[] jumpValues = {vector.vectorX, vector.vectorY, vector.vectorZ};
for (VectorData vector : new HashSet<>(existingVelocities)) {
Vector3dm jump = vector.vector.clone();
if (!player.isFlying) {
// Negative jump boost does not allow the player to leave the ground
@@ -59,17 +54,17 @@ public class PredictionEngineNormal extends PredictionEngine {
if (((jumpBoost.isEmpty() || jumpBoost.getAsInt() >= 0) && player.onGround) || !player.lastOnGround)
return;
JumpPower.jumpFromGround(player, jumpValues);
JumpPower.jumpFromGround(player, jump);
} else {
jumpValues[1] = player.flySpeed * 3;
jump.add(0, player.flySpeed * 3, 0);
if (!player.wasFlying) {
double[] edgeCaseJumpValues = {jumpValues[0], jumpValues[1], jumpValues[2]};
JumpPower.jumpFromGround(player, edgeCaseJumpValues);
existingVelocities.add(vector.returnNewModified(edgeCaseJumpValues[0], edgeCaseJumpValues[1], edgeCaseJumpValues[2], VectorData.VectorType.Jump));
Vector3dm edgeCaseJump = jump.clone();
JumpPower.jumpFromGround(player, edgeCaseJump);
existingVelocities.add(vector.returnNewModified(edgeCaseJump, VectorData.VectorType.Jump));
}
}
existingVelocities.add(vector.returnNewModified(jumpValues[0], jumpValues[1], jumpValues[2], VectorData.VectorType.Jump));
existingVelocities.add(vector.returnNewModified(jump, VectorData.VectorType.Jump));
}
}
@@ -91,30 +86,32 @@ public class PredictionEngineNormal extends PredictionEngine {
if (player.lastWasClimbing == 0 && (player.pointThreeEstimator.isNearClimbable() || player.isClimbing) && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14)
|| !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5))) || walkingOnPowderSnow) {
player.lastWasClimbing = staticVectorEndOfTickY(player, 0.2);
Vector3dm ladderVelocity = player.clientVelocity.clone().setY(0.2);
staticVectorEndOfTick(player, ladderVelocity);
player.lastWasClimbing = ladderVelocity.getY();
}
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector);
staticVectorEndOfTick(player, vector.vector);
}
}
@Override
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
if (player.isClimbing) {
// Reset fall distance when climbing
player.fallDistance = 0;
x = GrimMath.clamp(x, -0.15F, 0.15F);
z = GrimMath.clamp(z, -0.15F, 0.15F);
y = Math.max(y, -0.15F);
vector.setX(GrimMath.clamp(vector.getX(), -0.15F, 0.15F));
vector.setZ(GrimMath.clamp(vector.getZ(), -0.15F, 0.15F));
vector.setY(Math.max(vector.getY(), -0.15F));
// Yes, this uses shifting not crouching
if (y < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
y = 0.0;
if (vector.getY() < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
vector.setY(0.0);
}
}
return new Vector3dm(x, y, z);
return vector;
}
}
@@ -19,19 +19,12 @@ public class PredictionEngineWater extends PredictionEngine {
private double playerGravity;
private float swimmingFriction;
public static void staticVectorEndOfTick(GrimPlayer player, double[] vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector[0] *= swimmingFriction;
vector[1] *= 0.8F;
vector[2] *= swimmingFriction;
double y = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vector[1]);
vector[1] = y;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData, float swimmingFriction, double playerGravity, boolean isFalling) {
vectorData.vectorX *= swimmingFriction;
vectorData.vectorY *= 0.8F;
vectorData.vectorZ *= swimmingFriction;
vectorData.vectorY = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vectorData.vectorY);
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector.multiply(swimmingFriction, 0.8F, swimmingFriction);
Vector3dm fluidVector = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, vector);
vector.setX(fluidVector.getX());
vector.setY(fluidVector.getY());
vector.setZ(fluidVector.getZ());
}
public static Set<VectorData> transformSwimmingVectors(GrimPlayer player, Set<VectorData> base) {
@@ -52,7 +45,7 @@ public class PredictionEngineWater extends PredictionEngine {
double scalar = lookYAmount < -0.2 ? 0.085 : 0.06;
// The player can always press jump and activate this
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vectorX, vector.vectorY + ((lookYAmount - vector.vectorY) * scalar), vector.vectorZ), VectorData.VectorType.SwimmingSpace));
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vector.getX(), vector.vector.getY() + ((lookYAmount - vector.vector.getY()) * scalar), vector.vector.getZ()), VectorData.VectorType.SwimmingSpace));
// This scenario will occur if the player does not press jump and the other conditions are met
// Theoretically we should check this BEFORE allowing no look, but there isn't a cheat that takes advantage of this yet
@@ -61,7 +54,7 @@ public class PredictionEngineWater extends PredictionEngine {
// If you feel adventurous, re-add the following line to eliminate this unfair disadvantage
//if (d > 0.0 && player.world.getFluidLevelAt(player.lastX, player.lastY + 1.0 - 0.1, player.lastZ) == 0) {
swimmingVelocities.add(vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, VectorData.VectorType.SurfaceSwimming));
swimmingVelocities.add(vector.returnNewModified(vector.vector, VectorData.VectorType.SurfaceSwimming));
}
return swimmingVelocities;
@@ -79,21 +72,17 @@ public class PredictionEngineWater extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
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(startX, startY + 0.04f, startZ, 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) {
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
}
}
}
@@ -103,7 +92,7 @@ public class PredictionEngineWater extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector, swimmingFriction, playerGravity, isFalling);
staticVectorEndOfTick(player, vector.vector, swimmingFriction, playerGravity, isFalling);
}
}
@@ -112,7 +101,7 @@ public class PredictionEngineWater extends PredictionEngine {
// "hacky" climbing where player enters ladder within 0.03 movement (WHY DOES 0.03 EXIST???)
if (player.lastWasClimbing == 0 && player.pointThreeEstimator.isNearClimbable() && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) || !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
}
Set<VectorData> baseVelocities = super.fetchPossibleStartTickVectors(player);
@@ -20,8 +20,8 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
// This is just the vanilla equation for legacy water movement
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float f, float f2) {
float lengthSquared = (float) GrimMath.lengthSquared(x, y, z);
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float lengthSquared = (float) inputVector.lengthSquared();
if (lengthSquared >= 1.0E-4F) {
lengthSquared = (float) Math.sqrt(lengthSquared);
@@ -31,15 +31,13 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
}
lengthSquared = swimmingSpeed / lengthSquared;
x *= lengthSquared;
y *= lengthSquared;
z *= lengthSquared;
inputVector.multiply(lengthSquared);
float yawRadians = GrimMath.radians(player.yaw);
float sinResult = player.trigHandler.sin(yawRadians);
float cosResult = player.trigHandler.cos(yawRadians);
return new Vector3dm(x * cosResult - z * sinResult,
y, z * cosResult + x * sinResult);
return new Vector3dm(inputVector.getX() * cosResult - inputVector.getZ() * sinResult,
inputVector.getY(), inputVector.getZ() * cosResult + inputVector.getX() * sinResult);
}
return new Vector3dm();
@@ -48,12 +46,11 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
existingVelocities.add(new VectorData(vector.vectorX, vector.vectorY + 0.04f, vector.vectorZ, vector, VectorData.VectorType.Jump));
for (VectorData vector : new HashSet<>(existingVelocities)) {
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.04f, 0), vector, VectorData.VectorType.Jump));
if (player.skippedTickInActualMovement) {
existingVelocities.add(new VectorData(vector.vectorX,vector.vectorY + 0.02f, vector.vectorZ, vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(vector.vector.clone().add(0, 0.02f, 0), vector, VectorData.VectorType.Jump));
}
}
}
@@ -63,12 +60,10 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vectorX *= swimmingFriction;
vector.vectorY *= 0.8F;
vector.vectorZ *= swimmingFriction;
vector.vector.multiply(swimmingFriction, 0.8F, swimmingFriction);
// Gravity
vector.vectorY -= 0.02D;
vector.vector.setY(vector.vector.getY() - 0.02D);
}
}
}
@@ -7,7 +7,6 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.enums.BoatEntityStatus;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -147,7 +146,7 @@ public class PredictionEngineBoat extends PredictionEngine {
for (VectorData data : possibleVectors) {
// TODO: is this correct?
data.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
data.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
@@ -156,41 +155,14 @@ public class PredictionEngineBoat extends PredictionEngine {
// so if a player tries to move in both directions, a packet will
// show that the player is staying, but the boat will move anyway
if (player.vehicleData.vehicleForward == 0) {
float f = 0.035F;
double x = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
double y = data.vectorY;
double z = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
x *= player.stuckSpeedMultiplier.getX();
y *= player.stuckSpeedMultiplier.getY();
z *= player.stuckSpeedMultiplier.getZ();
}
vectors.add(data.returnNewModified(x, y, z, VectorData.VectorType.InputResult));
Vector3dm vector = data.vector.clone();
controlBoat(player, vector, true);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
vectors.add(data.returnNewModified(vector, VectorData.VectorType.InputResult));
}
float f;
if (player.vehicleData.vehicleHorizontal != 0) {
f = 0.005F;
if (player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
} else if (player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
} else {
f = 0.0F;
}
// TODO (Mutation) (Confirm if Correct)
// It seems possible for applyStuckSpeed = 0, and 1 to add the same vector to the set twice
// since we're mutating data.vectorX, Y, Z which is very likely incorrect but I don't know enough to say for certain
data.vectorX = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
data.vectorZ = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
data.vectorX *= player.stuckSpeedMultiplier.getX();
data.vectorY *= player.stuckSpeedMultiplier.getY();
data.vectorZ *= player.stuckSpeedMultiplier.getZ();
}
controlBoat(player, data.vector, false);
if (applyStuckSpeed != 0) data.vector.multiply(player.stuckSpeedMultiplier);
vectors.add(data);
}
}
@@ -204,7 +176,7 @@ public class PredictionEngineBoat extends PredictionEngine {
addFluidPushingToStartingVectors(player, vectors);
for (VectorData data : vectors) {
floatBoat(player, data);
floatBoat(player, data.vector);
}
return vectors;
@@ -225,7 +197,7 @@ public class PredictionEngineBoat extends PredictionEngine {
return false;
}
private void floatBoat(GrimPlayer player, VectorData vector) {
private void floatBoat(GrimPlayer player, Vector3dm vector) {
double d1 = player.hasGravity ? -0.04f : 0;
double d2 = 0.0D;
float invFriction = 0.05F;
@@ -236,7 +208,7 @@ public class PredictionEngineBoat extends PredictionEngine {
player.lastY = getWaterLevelAbove(player) - 0.5625F + 0.101D;
player.boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, player.lastX, player.lastY, player.lastZ);
player.actualMovement = new Vector3dm(player.x - player.lastX, player.y - player.lastY, player.z - player.lastZ);
vector.vectorY = 0;
vector.setY(0);
player.vehicleData.lastYd = 0.0D;
player.vehicleData.status = BoatEntityStatus.IN_WATER;
@@ -257,12 +229,12 @@ public class PredictionEngineBoat extends PredictionEngine {
player.vehicleData.landFriction /= 2.0F;
}
vector.vectorX *= invFriction;
vector.vectorY += d1;
vector.vectorZ *= invFriction;
vector.setX(vector.getX() * invFriction);
vector.setY(vector.getY() + d1);
vector.setZ(vector.getZ() * invFriction);
if (d2 > 0.0D) {
double yVel = vector.vectorY;
double yVel = vector.getY();
vector.setY((yVel + d2 * 0.06153846016296973D) * 0.75D);
}
}
@@ -300,4 +272,22 @@ public class PredictionEngineBoat extends PredictionEngine {
return (float) (l + 1);
}
private void controlBoat(GrimPlayer player, Vector3dm vector, boolean intermediate) {
float f = 0.0F;
if (player.vehicleData.vehicleHorizontal != 0 && (!intermediate && player.vehicleData.vehicleForward == 0)) {
f += 0.005F;
}
//player.boatData.yRot += player.boatData.deltaRotation;
if (intermediate || player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
}
if (intermediate || player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
vector.add(new Vector3dm(player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f, 0, (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f)));
}
}
@@ -18,9 +18,9 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
@Override
public void endOfTick(GrimPlayer player, double delta) {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vectorX *= multiplier;
vector.vectorY *= multiplier;
vector.vectorZ *= multiplier;
vector.vector.setX(vector.vector.getX() * multiplier);
vector.vector.setY(vector.vector.getY() * multiplier);
vector.vector.setZ(vector.vector.getZ() * multiplier);
}
}
@@ -30,15 +30,15 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
}
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float flyingSpeed, float yRot) {
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float flyingSpeed, float yRot) {
float yRotRadians = GrimMath.radians(yRot);
float sin = player.trigHandler.sin(yRotRadians);
float cos = player.trigHandler.cos(yRotRadians);
double xResult = x * cos - z * sin;
double zResult = z * cos + x * sin;
double xResult = inputVector.getX() * cos - inputVector.getZ() * sin;
double zResult = inputVector.getZ() * cos + inputVector.getX() * sin;
return new Vector3dm(xResult * flyingSpeed, y * flyingSpeed, zResult * flyingSpeed);
return new Vector3dm(xResult * flyingSpeed, inputVector.getY() * flyingSpeed, zResult * flyingSpeed);
}
}
@@ -7,7 +7,6 @@ import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
import ac.grim.grimac.utils.data.packetentity.PacketEntityHorse;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.JumpPower;
@@ -63,9 +62,7 @@ public final class PredictionEngineRideableUtils {
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.vectorX += jumpVelocity.getX();
vectorData.vectorY += jumpVelocity.getY();
vectorData.vectorZ += jumpVelocity.getZ();
vectorData.vector.add(jumpVelocity);
}
player.vehicleData.horseJumping = true;
@@ -105,10 +102,9 @@ public final class PredictionEngineRideableUtils {
float f3 = player.trigHandler.cos(yawRadians);
for (VectorData vectorData : possibleVectors) {
vectorData.vectorY = jumpVelocity;
vectorData.vector.setY(jumpVelocity);
if (forwardInput > 0.0F) {
vectorData.vectorX += -0.4F * f2 * player.vehicleData.horseJump;
vectorData.vectorZ += 0.4F * f3 * player.vehicleData.horseJump;
vectorData.vector.add(new Vector3dm(-0.4F * f2 * player.vehicleData.horseJump, 0.0D, 0.4F * f3 * player.vehicleData.horseJump));
}
}
@@ -126,24 +122,22 @@ public final class PredictionEngineRideableUtils {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
Vector3dm vector3dm = predictionEngine.getMovementResultFromInput(player, movementVector.getX(), movementVector.getY(), movementVector.getZ(), speed, player.yaw).add(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ);
VectorData result = new VectorData(vector3dm, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
Vector3dm vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
VectorData result = new VectorData(possibleLastTickOutput.vector.clone().add(predictionEngine.getMovementResultFromInput(player, movementVector, speed, player.yaw)), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
Vector3dm vector = result.vector.clone();
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
returnVectors.add(result);
// This is the laziest way to reduce false positives such as horse rearing
// No bypasses can ever be derived from this, so why not?
result = new VectorData(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
result = new VectorData(possibleLastTickOutput.vector.clone(), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
vector = result.vector.clone();
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
returnVectors.add(result);
}
}
@@ -30,14 +30,6 @@ public class MessageUtil {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
public @NotNull String toUnlabeledString(int x, int y, int z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabeledString(double x, double y, double z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabledString(@Nullable Vector3f vec) {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
@@ -3,6 +3,8 @@ package ac.grim.grimac.utils.anticheat;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.event.events.GrimJoinEvent;
import ac.grim.grimac.api.event.events.GrimQuitEvent;
import ac.grim.grimac.checks.debug.DebugGrimPlayer;
import ac.grim.grimac.checks.debug.GrimDebugSettings;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.reflection.GeyserUtil;
import com.github.retrooper.packetevents.PacketEvents;
@@ -66,12 +68,21 @@ public class PlayerDataManager {
public void addUser(final @NotNull User user) {
if (shouldCheck(user)) {
GrimPlayer player = new GrimPlayer(user);
GrimPlayer player = this.createPlayer(user);
playerDataMap.put(user, player);
GrimAPI.INSTANCE.getEventBus().post(new GrimJoinEvent(player));
}
}
public GrimPlayer createPlayer(User user) {
// Simple Global Check
if (GrimDebugSettings.shouldEnable()) {
return new DebugGrimPlayer(user);
} else {
return new GrimPlayer(user);
}
}
public GrimPlayer remove(final @NotNull User user) {
return playerDataMap.remove(user);
}
@@ -14,9 +14,7 @@ import java.util.ArrayList;
import java.util.List;
public final class BlockBreak {
public final int x;
public final int y;
public final int z;
public final Vector3i position;
public final BlockFace face;
public final int faceId;
public final DiggingAction action;
@@ -26,11 +24,9 @@ public final class BlockBreak {
@Getter
private boolean cancelled;
public BlockBreak(GrimPlayer player, final int x, final int y, final int z, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
public BlockBreak(GrimPlayer player, Vector3i position, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
this.player = player;
this.x = x;
this.y = y;
this.z = z;
this.position = position;
this.face = face;
this.faceId = faceId;
this.action = action;
@@ -43,12 +39,12 @@ public final class BlockBreak {
}
public SimpleCollisionBox getCombinedBox() {
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, x, y, z);
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, position.x, position.y, position.z);
List<SimpleCollisionBox> boxes = new ArrayList<>();
placedOn.downCast(boxes);
SimpleCollisionBox combined = new SimpleCollisionBox(x, y, z);
SimpleCollisionBox combined = new SimpleCollisionBox(position.x, position.y, position.z);
for (SimpleCollisionBox box : boxes) {
double minX = Math.max(box.minX, combined.minX);
double minY = Math.max(box.minY, combined.minY);
@@ -568,10 +568,8 @@ public class BlockPlace {
// This happens due to the lack of an idle packet on 1.9+ clients
// On 1.8 clients this should practically never happen
if (interpWidth - width > 0.05 || interpHeight - height > 0.05) {
final double x = entity.trackedServerPosition.getX();
final double y = entity.trackedServerPosition.getX();
final double z = entity.trackedServerPosition.getX();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, x, y, z, entity);
Vector3d entityPos = entity.trackedServerPosition.getPos();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, entityPos.getX(), entityPos.getY(), entityPos.getZ(), entity);
}
if (box.isIntersected(interpBox)) {
@@ -643,13 +641,9 @@ public class BlockPlace {
Vector3dm look = ReachUtils.getLook(player, player.yaw, player.pitch);
final double distance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE) + 3;
final double eyeX = player.x;
final double eyeY = player.y + player.getEyeHeight();
final double eyeZ = player.z;
final double endX = eyeX + look.getX() * distance;
final double endY = eyeY + look.getY() * distance;
final double endZ = eyeZ + look.getZ() * distance;
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyeX, eyeY, eyeZ, endX, endY, endZ).first();
Vector3dm eyePos = new Vector3dm(player.x, player.y + player.getEyeHeight(), player.z);
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(look.getX() * distance, look.getY() * distance, look.getZ() * distance));
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyePos, endReachPos).first();
// Bring this back to relative to the block
// The player didn't even click the block... (we should force resync BEFORE we get here!)
@@ -11,12 +11,7 @@ import lombok.Setter;
@Getter
@Setter
public final class PositionUpdate {
private final double fromX;
private final double fromY;
private final double fromZ;
private final double toX;
private final double toY;
private final double toZ;
private final Vector3d from, to;
private final boolean onGround;
private final SetBackData setback;
private final TeleportData teleportData;
@@ -1,3 +1,5 @@
package ac.grim.grimac.utils.anticheat.update;
public record VehiclePositionUpdate(double fromX, double fromY, double fromZ, double toX, double toY, double toZ, float xRot, float yRot, boolean isTeleport) {}
import com.github.retrooper.packetevents.util.Vector3d;
public record VehiclePositionUpdate(Vector3d from, Vector3d to, float xRot, float yRot, boolean isTeleport) {}
@@ -7,17 +7,15 @@ import org.jetbrains.annotations.NotNull;
public record BlockModification(
WrappedBlockState oldBlockContents,
WrappedBlockState newBlockContents,
int x,
int y,
int z,
Vector3i location,
int tick,
Cause cause
) {
@Override
public @NotNull String toString() {
return String.format(
"BlockModification{x=%d, y=%d, z=%d, old=%s, new=%s, tick=%d, cause=%s}",
x, y, z, oldBlockContents, newBlockContents, tick, cause
"BlockModification{location=%s, old=%s, new=%s, tick=%d, cause=%s}",
location, oldBlockContents, newBlockContents, tick, cause
);
}
@@ -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;
@@ -56,7 +56,7 @@ public class SimpleCollisionBox implements CollisionBox {
}
public SimpleCollisionBox(Vector3i pos) {
this(pos.getX(), pos.getY(), pos.getZ());
this(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
}
// If you want to set a full block from a point
@@ -412,16 +412,16 @@ public class SimpleCollisionBox implements CollisionBox {
// Copied from hawk lol
// I would like to point out that this is magic to me and I have not attempted to understand this code
public Vector3dm intersectsRay(Ray ray, float minDist, float maxDist) {
Vector3dm invDir = new Vector3dm(1f / ray.getDirX(), 1f / ray.getDirY(), 1f / ray.getDirZ());
Vector3dm invDir = new Vector3dm(1f / ray.getDirection().getX(), 1f / ray.getDirection().getY(), 1f / ray.getDirection().getZ());
boolean signDirX = invDir.getX() < 0;
boolean signDirY = invDir.getY() < 0;
boolean signDirZ = invDir.getZ() < 0;
double tmin = ((signDirX ? maxX : minX) - ray.getOriginX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOriginX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOriginY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOriginY()) * invDir.getY();
double tmin = ((signDirX ? maxX : minX) - ray.getOrigin().getX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOrigin().getX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOrigin().getY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOrigin().getY()) * invDir.getY();
if (tmin > tymax || tymin > tmax) {
return null;
@@ -430,8 +430,8 @@ public class SimpleCollisionBox implements CollisionBox {
if (tymin > tmin) tmin = tymin;
if (tymax < tmax) tmax = tymax;
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOriginZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOriginZ()) * invDir.getZ();
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOrigin().getZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOrigin().getZ()) * invDir.getZ();
if ((tmin > tzmax) || (tzmin > tmax)) {
return null;
@@ -29,4 +29,10 @@ public class CommonGrimArguments {
* 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));
/**
* If TRUE, and a Debugger is detected attached to the JVM, Grim will use the
* DebugGrimPlayer implementation which absorbs breakpoint pauses > 200ms.
* Default: false (Safety).
*/
public final static SystemArgument<Boolean> DEBUG_BREAKPOINT_MODE = FACTORY.create(string("DebugBreakpointMode", false));
}
@@ -39,11 +39,12 @@ public class ReachInterpolationData {
private boolean expandNonRelative = false;
private int cancelledLerpInterpolationStepsLowBound = Integer.MAX_VALUE;
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, final double x, final double y, final double z, PacketEntity entity) {
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, TrackedPosition position, PacketEntity entity) {
final boolean unreliableTicking = !player.inVehicle() && player.canSkipTicks();
this.startingLocation = startingLocation;
this.targetLocation = new SimpleCollisionBox(x, y, z, x, y, z, false);
final Vector3d pos = position.getPos();
this.targetLocation = new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false);
this.player = player;
this.entity = entity;
@@ -11,34 +11,22 @@ import lombok.ToString;
public class SetBackData {
private final TeleportData teleportData;
private final float xRot, yRot;
private final double velocityX, velocityY, velocityZ;
private boolean hasVelocity;
private final Vector3dm velocity;
private final boolean vehicle;
private boolean isComplete = false;
// TODO: Rethink when we block movements for teleports, perhaps after 10 ticks or 5 blocks?
private boolean isPlugin;
private int ticksComplete = 0;
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean hasVelocity, boolean vehicle, boolean isPlugin) {
public SetBackData(TeleportData teleportData, float xRot, float yRot, Vector3dm velocity, boolean vehicle, boolean isPlugin) {
this.teleportData = teleportData;
this.xRot = xRot;
this.yRot = yRot;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
this.hasVelocity = hasVelocity;
this.velocity = velocity;
this.vehicle = vehicle;
this.isPlugin = isPlugin;
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, velocityX, velocityY, velocityZ, true, vehicle, isPlugin);
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, 0, 0, 0, false, vehicle, isPlugin);
}
public void tick() {
if (isComplete) ticksComplete++;
}
@@ -12,13 +12,9 @@ import org.jetbrains.annotations.NotNull;
@AllArgsConstructor
@Getter
public class TeleportData {
private final double locationX;
private final double locationY;
private final double locationZ;
private final double velocityX;
private final double velocityY;
private final double velocityZ;
private final @NotNull RelativeFlag flags;
private final Vector3d location;
private final Vector3d velocity;
private final RelativeFlag flags;
@Setter
private int transaction;
@Setter
@@ -40,27 +36,29 @@ public class TeleportData {
if (!isRelativeZ()) {
vector.setZ(0);
}
} else {
}
if (velocity != null && isStupidTeleportSystem) {
// WHAT WAS MOJANG THINKING MAKING TELEPORTS A REPLACEMENT PACKET FOR EXPLOSION VELOCITY /s
if (isRelativeDeltaX()) {
vector.setX(vector.getX() + velocityX);
vector.setX(vector.getX() + velocity.getX());
} else {
vector.setX(velocityX);
vector.setX(velocity.getX());
}
if (isRelativeDeltaY()) {
vector.setY(vector.getY() + velocityY);
vector.setY(vector.getY() + velocity.getY());
} else {
vector.setY(velocityY);
vector.setY(velocity.getY());
// Is this correct? Don't know don't care.
player.lastWasClimbing = 0; // Vertical movement reset
player.canSwimHop = false; // Vertical movement reset
}
if (isRelativeDeltaZ()) {
vector.setZ(vector.getZ() + velocityZ);
vector.setZ(vector.getZ() + velocity.getZ());
} else {
vector.setZ(velocityZ);
vector.setZ(velocity.getZ());
}
}
}
@@ -11,9 +11,8 @@ public final class TrackedPosition {
private static final double LEGACY_COORDINATE_SCALE = 32.0;
private final double scale;
private double x;
private double y;
private double z;
@Setter
private Vector3d pos = new Vector3d();
public TrackedPosition() {
// this.scale = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? MODERN_COORDINATE_SCALE : LEGACY_COORDINATE_SCALE;
@@ -36,38 +35,23 @@ public final class TrackedPosition {
return value / scale;
}
// Methods since 1.16.
public double withDeltaX(long x) {
if (x == 0L) return this.x;
return unpack(pack(this.x, scale) + x);
// Method since 1.16.
public Vector3d withDelta(long x, long y, long z) {
if (x == 0L && y == 0L && z == 0L) {
return this.pos;
}
double d = x == 0L ? this.pos.x : unpack(pack(this.pos.x, scale) + x);
double e = y == 0L ? this.pos.y : unpack(pack(this.pos.y, scale) + y);
double f = z == 0L ? this.pos.z : unpack(pack(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
}
public double withDeltaY(long y) {
if (y == 0L) return this.y;
return unpack(pack(this.y, scale) + y);
}
public double withDeltaZ(long z) {
if (z == 0L) return this.z;
return unpack(pack(this.z, scale) + z);
}
// In 1.16-, these were different.
public double withDeltaLegacyX(double x) {
return unpackLegacy(packLegacy(this.x, scale) + x);
}
public double withDeltaLegacyY(double y) {
return unpackLegacy(packLegacy(this.y, scale) + y);
}
public double withDeltaLegacyZ(double z) {
return unpackLegacy(packLegacy(this.z, scale) + z);
}
public void setPos(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
// In 1.16-, this was different.
public Vector3d withDeltaLegacy(double x, double y, double z) {
double d = unpackLegacy(packLegacy(this.pos.x, scale) + x);
double e = unpackLegacy(packLegacy(this.pos.y, scale) + y);
double f = unpackLegacy(packLegacy(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
}
}
@@ -1,7 +1,5 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -12,33 +10,15 @@ public class VectorData {
public final VectorType vectorType;
public VectorData lastVector;
public VectorData preUncertainty;
public double vectorX, vectorY, vectorZ;
public @MonotonicNonNull Vec2d input;
public Vector3dm vector;
public @MonotonicNonNull Vector3dm input;
@Getter
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false;
// For handling replacing the type of vector it is while keeping data
public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) {
this(vector.getX(), vector.getY(), vector.getZ(), lastVector, vectorType);
}
public void setX(double x) {
this.vectorX = x;
}
public void setY(double y) {
this.vectorY = y;
}
public void setZ(double z) {
this.vectorZ = z;
}
public VectorData(final double x, final double y, final double z, VectorData lastVector, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.vector = vector;
this.lastVector = lastVector;
this.vectorType = vectorType;
@@ -62,40 +42,30 @@ public class VectorData {
}
public VectorData(Vector3dm vector, VectorType vectorType) {
this(vector.getX(), vector.getY(), vector.getZ(), vectorType);
}
public VectorData(final double x, final double y, final double z, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.vector = vector;
this.vectorType = vectorType;
addVectorType(vectorType);
}
public VectorData returnNewModified(VectorType type) {
return new VectorData(this.vectorX, this.vectorY, this.vectorZ, this, type);
return new VectorData(vector, this, type);
}
public VectorData returnNewModified(Vector3dm newVec, VectorType type) {
return new VectorData(newVec, this, type);
}
public VectorData returnNewModified(final double x, final double y, final double z, VectorType type) {
return new VectorData(x, y, z, this, type);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VectorData that = (VectorData) o;
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && vectorX == that.vectorX && vectorY == that.vectorY && vectorZ == that.vectorZ;
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && Objects.equals(vector, that.vector);
}
@Override
public int hashCode() {
return Objects.hash(vectorType, lastVector, preUncertainty, vectorX, vectorY, vectorZ, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
return Objects.hash(vectorType, lastVector, preUncertainty, vector, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
}
public void addVectorType(VectorType type) {
@@ -118,7 +88,7 @@ public class VectorData {
public String toString() {
return "VectorData{" +
"pointThree=" + isZeroPointZeroThree +
", vector=" + MessageUtil.toUnlabeledString(vectorX, vectorY, vectorZ) +
", vector=" + vector +
'}';
}
@@ -20,11 +20,9 @@ public class VehicleData {
public float vehicleForward = 0f;
public boolean lastDummy = false;
public boolean wasVehicleSwitch = false;
public final ConcurrentLinkedQueue<VehicleTeleport> vehicleTeleports = new ConcurrentLinkedQueue<>();
public final ConcurrentLinkedQueue<Pair<Integer, Vector3d>> vehicleTeleports = new ConcurrentLinkedQueue<>();
public float nextHorseJump = 0;
public float horseJump = 0;
public boolean horseJumping = false;
public int camelDashCooldown;
public record VehicleTeleport(int teleportId, double x, double y, double z) {}
}
@@ -73,14 +73,12 @@ public class PacketEntity extends TypedPacketEntity {
this.uuid = uuid;
initAttributes(player);
this.trackedServerPosition = new TrackedPosition();
this.trackedServerPosition.setPos(x, y, z);
this.trackedServerPosition.setPos(new Vector3d(x, y, z));
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { // Thanks ViaVersion
trackedServerPosition.setPos(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d);
trackedServerPosition.setPos(new Vector3d(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d));
}
final double xPos = trackedServerPosition.getX();
final double yPos = trackedServerPosition.getY();
final double zPos = trackedServerPosition.getZ();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(xPos, yPos, zPos, xPos, yPos, zPos, false), xPos, yPos, zPos, this);
final Vector3d pos = trackedServerPosition.getPos();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false), trackedServerPosition, this);
}
protected void trackAttribute(ValuedAttribute valuedAttribute) {
@@ -131,31 +129,25 @@ public class PacketEntity extends TypedPacketEntity {
if (relative) {
// This only matters for 1.9+ clients, but it won't hurt 1.8 clients either... align for imprecision
final double scale = trackedServerPosition.getScale();
final double x;
final double y;
final double z;
Vector3d vec3d;
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16)) {
x = trackedServerPosition.withDeltaX(TrackedPosition.pack(relX, scale));
y = trackedServerPosition.withDeltaY(TrackedPosition.pack(relY, scale));
z = trackedServerPosition.withDeltaZ(TrackedPosition.pack(relZ, scale));
vec3d = trackedServerPosition.withDelta(TrackedPosition.pack(relX, scale), TrackedPosition.pack(relY, scale), TrackedPosition.pack(relZ, scale));
} else {
x = trackedServerPosition.withDeltaLegacyX(TrackedPosition.packLegacy(relX, scale));
y = trackedServerPosition.withDeltaLegacyY(TrackedPosition.packLegacy(relY, scale));
z = trackedServerPosition.withDeltaLegacyZ(TrackedPosition.packLegacy(relZ, scale));
vec3d = trackedServerPosition.withDeltaLegacy(TrackedPosition.packLegacy(relX, scale), TrackedPosition.packLegacy(relY, scale), TrackedPosition.packLegacy(relZ, scale));
}
trackedServerPosition.setPos(x, y, z);
trackedServerPosition.setPos(vec3d);
} else {
trackedServerPosition.setPos(relX, relY, relZ);
trackedServerPosition.setPos(new Vector3d(relX, relY, relZ));
// ViaVersion desync's here for teleports
// It simply teleports the entity with its position divided by 32... ignoring the offset this causes.
// Thanks a lot ViaVersion! Please don't fix this, or it will be a pain to support.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
trackedServerPosition.setPos(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d);
trackedServerPosition.setPos(new Vector3d(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d));
}
}
}
this.oldPacketLocation = newPacketLocation;
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition.getX(), trackedServerPosition.getY(), trackedServerPosition.getZ(), this);
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition, this);
// TODO make config option to rewrite Rots to PosRots instead of expanding to handle this false
// https://bugs.mojang.com/browse/MC-255263
@@ -216,7 +208,7 @@ public class PacketEntity extends TypedPacketEntity {
public void setPositionRaw(GrimPlayer player, SimpleCollisionBox box) {
// I'm disappointed in you mojang. Please don't set the packet position as it desyncs it...
// But let's follow this flawed client-sided logic!
this.trackedServerPosition.setPos((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ);
this.trackedServerPosition.setPos(new Vector3d((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ));
// This disables interpolation
this.newPacketLocation = new ReachInterpolationData(player, box, this);
}
@@ -1,8 +0,0 @@
package ac.grim.grimac.utils.function;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@FunctionalInterface
public interface BlockStateAtPosPredicate {
boolean test(WrappedBlockState state, int x, int y, int z);
}
@@ -1,6 +0,0 @@
package ac.grim.grimac.utils.function;
@FunctionalInterface
public interface Int3Consumer {
void accept(int x, int y, int z);
}
@@ -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;
}
}
}
@@ -501,6 +501,7 @@ public class CompensatedInventory extends Check implements PacketCheck {
* Closes the player's currently open inventory on the client by resetting to the player's inventory.
*/
private void closeActiveInventory() {
isPacketInventoryActive = true;
openWindowID = 0;
menu = inventory;
menu.setCarried(ItemStack.EMPTY); // Reset carried item
@@ -7,6 +7,7 @@ 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.packetentity.PacketEntity;
@@ -54,7 +55,6 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import lombok.Getter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -83,16 +83,14 @@ public class CompensatedWorld {
// The owning list is so that this info can be removed when the final list is processed
private final Long2ObjectOpenHashMap<BlockPrediction> originalServerBlocks = new Long2ObjectOpenHashMap<>();
// Blocks the client changed while placing or breaking blocks
private List<Vector3i> currentlyChangedBlocks = new ArrayList<>();
private List<Vector3i> currentlyChangedBlocks = new LinkedList<>();
private final Int2ObjectMap<List<Vector3i>> serverIsCurrentlyProcessingThesePredictions = new Int2ObjectOpenHashMap<>();
private final Object2ObjectLinkedOpenHashMap<UnAckedAction, Vector3d> unackedActions = new Object2ObjectLinkedOpenHashMap<>();
private final Object2ObjectLinkedOpenHashMap<Pair<Vector3i, DiggingAction>, Vector3d> unackedActions = new Object2ObjectLinkedOpenHashMap<>();
private boolean isCurrentlyPredicting = false;
public boolean isRaining = false;
private final boolean noNegativeBlocks;
private record UnAckedAction(int x, int y, int z, DiggingAction action) {}
public CompensatedWorld(GrimPlayer player) {
this.player = player;
chunks = new Long2ObjectOpenHashMap<>(81, 0.5f);
@@ -116,15 +114,15 @@ public class CompensatedWorld {
}
}
public void handleBlockBreakAck(final int x, final int y, final int z, int blockState, DiggingAction action, boolean accepted) {
if (!accepted || action != DiggingAction.START_DIGGING || !unackedActions.containsKey(new UnAckedAction(x, y, z, action))) {
public void handleBlockBreakAck(Vector3i blockPos, int blockState, DiggingAction action, boolean accepted) {
if (!accepted || action != DiggingAction.START_DIGGING || !unackedActions.containsKey(new Pair<>(blockPos, action))) {
player.sendTransaction(); // This packet actually matters
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
Vector3d playerPos = unackedActions.remove(new UnAckedAction(x, y, z, action));
handleAck(x, y, z, blockState, playerPos);
Vector3d playerPos = unackedActions.remove(new Pair<>(blockPos, action));
handleAck(blockPos, blockState, playerPos);
});
} else {
unackedActions.remove(new UnAckedAction(x, y, z, action));
unackedActions.remove(new Pair<>(blockPos, action));
}
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
@@ -137,38 +135,34 @@ public class CompensatedWorld {
private void applyBlockChanges(List<Vector3i> toApplyBlocks) {
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> toApplyBlocks.forEach(vector3i -> {
final int x = vector3i.getX();
final int y = vector3i.getY();
final int z = vector3i.getZ();
long serializedPosition = GrimMath.getSerializedPosition(x, y, z);
BlockPrediction predictionData = originalServerBlocks.get(serializedPosition);
BlockPrediction predictionData = originalServerBlocks.get(vector3i.getSerializedPosition());
// We are the last to care about this prediction, remove it to stop memory leak
// Block changes are allowed to execute out of order, because it actually doesn't matter
if (predictionData != null && predictionData.getForBlockUpdate() == toApplyBlocks) {
originalServerBlocks.remove(serializedPosition);
handleAck(x, y, z, predictionData.getOriginalBlockId(), predictionData.getPlayerPosition());
originalServerBlocks.remove(vector3i.getSerializedPosition());
handleAck(vector3i, predictionData.getOriginalBlockId(), predictionData.getPlayerPosition());
}
}));
}
private void handleAck(final int x, final int y, final int z, int originalBlockId, Vector3d playerPosition) {
private void handleAck(Vector3i vector3i, int originalBlockId, Vector3d playerPosition) {
// If we need to change the world block state
if (getBlock(x, y , z).getGlobalId() != originalBlockId) {
if (getBlock(vector3i).getGlobalId() != originalBlockId) {
player.blockHistory.add(
new BlockModification(
getBlock(x, y ,z),
getBlock(vector3i),
WrappedBlockState.getByGlobalId(originalBlockId),
x, y, z,
vector3i,
GrimAPI.INSTANCE.getTickManager().currentTick,
BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION
)
);
updateBlock(x, y, z, originalBlockId);
updateBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ(), originalBlockId);
WrappedBlockState state = WrappedBlockState.getByGlobalId(blockVersion, originalBlockId);
// The player will teleport themselves if they get stuck in the reverted block
if (playerPosition != null && CollisionData.getData(state.getType()).getMovementCollisionBox(player, player.getClientVersion(), state, x, y, z).isIntersected(player.boundingBox)) {
if (playerPosition != null && CollisionData.getData(state.getType()).getMovementCollisionBox(player, player.getClientVersion(), state, vector3i.getX(), vector3i.getY(), vector3i.getZ()).isIntersected(player.boundingBox)) {
player.lastX = player.x;
player.lastY = player.y;
player.lastZ = player.z;
@@ -183,11 +177,7 @@ public class CompensatedWorld {
public void handleBlockBreakPrediction(WrapperPlayClientPlayerDigging digging) {
// 1.14.4 intentional and correct, do not change it to 1.14
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14_4) && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_18_2)) {
Vector3i blockPosition = digging.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
unackedActions.put(new UnAckedAction(x, y, z, digging.getAction()), new Vector3d(player.x, player.y, player.z));
unackedActions.put(new Pair<>(digging.getBlockPosition(), digging.getAction()), new Vector3d(player.x, player.y, player.z));
}
}
@@ -198,7 +188,7 @@ public class CompensatedWorld {
if (this.currentlyChangedBlocks.isEmpty()) return; // Nothing to change
List<Vector3i> toApplyBlocks = this.currentlyChangedBlocks; // We must now track the client applying the server predicted blocks
this.currentlyChangedBlocks = new ArrayList<>(); // Reset variable without changing original
this.currentlyChangedBlocks = new LinkedList<>(); // Reset variable without changing original
// We don't need to simulate any packets, it is native to the version we are on
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
@@ -273,13 +263,12 @@ public class CompensatedWorld {
}
public void updateBlock(int x, int y, int z, int combinedID) {
long serializedPosition = GrimMath.getSerializedPosition(x, y, z);
BlockPrediction prediction = originalServerBlocks.get(serializedPosition);
Vector3i asVector = new Vector3i(x, y, z);
BlockPrediction prediction = originalServerBlocks.get(asVector.getSerializedPosition());
if (isCurrentlyPredicting) {
Vector3i asVector = new Vector3i(x , y, z);
if (prediction == null) {
originalServerBlocks.put(serializedPosition, new BlockPrediction(currentlyChangedBlocks, asVector, getBlock(x, y, z).getGlobalId(), new Vector3d(player.x, player.y, player.z))); // Remember server controlled block type
originalServerBlocks.put(asVector.getSerializedPosition(), new BlockPrediction(currentlyChangedBlocks, asVector, getBlock(asVector).getGlobalId(), new Vector3d(player.x, player.y, player.z))); // Remember server controlled block type
} else {
prediction.setForBlockUpdate(currentlyChangedBlocks); // Block existing there was placed by client, mark block to have a new prediction
}
@@ -637,7 +626,7 @@ public class CompensatedWorld {
}
public boolean containsLiquid(SimpleCollisionBox var0) {
return Collisions.hasMaterial(player, var0, data -> Materials.isWater(player.getClientVersion(), data) || data.getType() == StateTypes.LAVA);
return Collisions.hasMaterial(player, var0, data -> Materials.isWater(player.getClientVersion(), data.first()) || data.first().getType() == StateTypes.LAVA);
}
public double getLavaFluidLevelAt(int x, int y, int z) {
@@ -659,7 +648,7 @@ public class CompensatedWorld {
}
public boolean containsLava(SimpleCollisionBox var0) {
return Collisions.hasMaterial(player, var0, data -> data.getType() == StateTypes.LAVA);
return Collisions.hasMaterial(player, var0, data -> data.first().getType() == StateTypes.LAVA);
}
public double getWaterFluidLevelAt(double x, double y, double z) {
@@ -1,7 +1,5 @@
package ac.grim.grimac.utils.math;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.Contract;
@@ -235,38 +233,4 @@ public class GrimMath {
public static double square(double num) {
return num * num;
}
public static double distanceSquared(double x1, double y1, double z1, double x2, double y2, double z2) {
return GrimMath.square(x1 - x2) + GrimMath.square(y1 - y2) + GrimMath.square(z1 - z2);
}
private static final ServerVersion serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
public static long getSerializedPosition(int x, int y, int z) {
// 1.17 adds support for negative values
if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_17)) {
long lx = x & 0x3FFFFFF;
long ly = y & 0xFFF;
long lz = z & 0x3FFFFFF;
return lx << 38 | lz << 12 | ly;
// 1.14 method for this is storing X Z Y
} else if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_14)) {
return ((long) (x & 0x3FFFFFF) << 38) | ((long) (z & 0x3FFFFFF) << 12) | (y & 0xFFF);
}
// 1.13 and below store X Y Z
return ((long) (x & 0x3FFFFFF) << 38) | ((long) (y & 0xFFF) << 26) | (z & 0x3FFFFFF);
}
public static double lengthSquared(double x, double z) {
return GrimMath.square(x) + GrimMath.square(z);
}
public static double lengthSquared(double x, double y, double z) {
return GrimMath.square(x) + GrimMath.square(y) + GrimMath.square(z);
}
public static double length(double x, double y, double z) {
return Math.sqrt(lengthSquared(x, y, z));
}
}
@@ -1,3 +0,0 @@
package ac.grim.grimac.utils.math;
public record Vec2d(double x, double z) {}
@@ -8,8 +8,8 @@ import org.jetbrains.annotations.NotNull;
@UtilityClass
public class VectorUtils {
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorToCutTo, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
SimpleCollisionBox box = new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ).sort();
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorToCutTo, @NotNull Vector3dm min, @NotNull Vector3dm max) {
SimpleCollisionBox box = new SimpleCollisionBox(min, max).sort();
return cutBoxToVector(vectorToCutTo, box);
}
@@ -32,14 +32,10 @@ public class VectorUtils {
// Clamping stops the player from causing an integer overflow and crashing the netty thread
@Contract("_ -> new")
public static @NotNull Vector3d clampVector(@NotNull Vector3d toClamp) {
return clampVector(toClamp.getX(), toClamp.getY(), toClamp.getZ());
}
double x = GrimMath.clamp(toClamp.getX(), -3.0E7D, 3.0E7D);
double y = GrimMath.clamp(toClamp.getY(), -2.0E7D, 2.0E7D);
double z = GrimMath.clamp(toClamp.getZ(), -3.0E7D, 3.0E7D);
@Contract("_ -> new")
public static @NotNull Vector3d clampVector(double toClampX, double toClampY, double toClampZ) {
double x = GrimMath.clamp(toClampX, -3.0E7D, 3.0E7D);
double y = GrimMath.clamp(toClampY, -2.0E7D, 2.0E7D);
double z = GrimMath.clamp(toClampZ, -3.0E7D, 3.0E7D);
return new Vector3d(x, y, z);
}
}
@@ -27,33 +27,33 @@ public final class BoundingBoxSize {
private static float getWidthMinusBaby(GrimPlayer player, PacketEntity packetEntity) {
final EntityType type = packetEntity.type;
if (EntityTypes.AXOLOTL.equals(type)) {
if (type == EntityTypes.AXOLOTL) {
return 0.75f;
} else if (EntityTypes.PANDA.equals(type)) {
} else if (type == EntityTypes.PANDA) {
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) || EntityTypes.COPPER_GOLEM.equals(type)) {
} else if (type == EntityTypes.BAT || type == EntityTypes.PARROT || type == EntityTypes.COD || type == EntityTypes.EVOKER_FANGS || type == EntityTypes.TROPICAL_FISH || type == EntityTypes.FROG || type == EntityTypes.COPPER_GOLEM) {
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)) {
} else if (type == EntityTypes.ARMADILLO || type == EntityTypes.BEE || type == EntityTypes.PUFFERFISH || type == EntityTypes.SALMON || type == EntityTypes.SNOW_GOLEM || type == EntityTypes.CAVE_SPIDER) {
return 0.7f;
} else if (EntityTypes.WITHER_SKELETON.equals(type)) {
} else if (type == EntityTypes.WITHER_SKELETON) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.7f : 0.72f;
} else if (EntityTypes.WITHER_SKULL.equals(type) || EntityTypes.SHULKER_BULLET.equals(type)) {
} else if (type == EntityTypes.WITHER_SKULL || type == EntityTypes.SHULKER_BULLET) {
return 0.3125f;
} else if (EntityTypes.HOGLIN.equals(type) || EntityTypes.ZOGLIN.equals(type)) {
} else if (type == EntityTypes.HOGLIN || type == EntityTypes.ZOGLIN) {
return 1.3964844f;
} else if (EntityTypes.SKELETON_HORSE.equals(type) || EntityTypes.ZOMBIE_HORSE.equals(type) || EntityTypes.HORSE.equals(type) || EntityTypes.DONKEY.equals(type) || EntityTypes.MULE.equals(type)) {
} else if (type == EntityTypes.SKELETON_HORSE || type == EntityTypes.ZOMBIE_HORSE || type == EntityTypes.HORSE || type == EntityTypes.DONKEY || type == EntityTypes.MULE) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 1.3964844f : 1.4f;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.BOAT)) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 1.375f : 1.5f;
} else if (EntityTypes.HAPPY_GHAST.equals(type)) {
} else if (type == EntityTypes.HAPPY_GHAST) {
return 4.0f;
} else if (EntityTypes.CHICKEN.equals(type) || EntityTypes.ENDERMITE.equals(type) || EntityTypes.SILVERFISH.equals(type) || EntityTypes.VEX.equals(type) || EntityTypes.TADPOLE.equals(type)) {
} else if (type == EntityTypes.CHICKEN || type == EntityTypes.ENDERMITE || type == EntityTypes.SILVERFISH || type == EntityTypes.VEX || type == EntityTypes.TADPOLE) {
return 0.4f;
} else if (EntityTypes.RABBIT.equals(type)) {
} else if (type == EntityTypes.RABBIT) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.4f : 0.6f;
} else if (EntityTypes.CREAKING.equals(type) || EntityTypes.STRIDER.equals(type) || EntityTypes.COW.equals(type) || EntityTypes.SHEEP.equals(type) || EntityTypes.MOOSHROOM.equals(type) || EntityTypes.PIG.equals(type) || EntityTypes.LLAMA.equals(type) || EntityTypes.DOLPHIN.equals(type) || EntityTypes.WITHER.equals(type) || EntityTypes.TRADER_LLAMA.equals(type) || EntityTypes.WARDEN.equals(type) || EntityTypes.GOAT.equals(type)) {
} else if (type == EntityTypes.CREAKING || type == EntityTypes.STRIDER || type == EntityTypes.COW || type == EntityTypes.SHEEP || type == EntityTypes.MOOSHROOM || type == EntityTypes.PIG || type == EntityTypes.LLAMA || type == EntityTypes.DOLPHIN || type == EntityTypes.WITHER || type == EntityTypes.TRADER_LLAMA || type == EntityTypes.WARDEN || type == EntityTypes.GOAT) {
return 0.9f;
} else if (EntityTypes.PHANTOM.equals(type)) {
} else if (type == EntityTypes.PHANTOM) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
return 0.9f + sizeable.size * 0.2f;
}
@@ -61,21 +61,21 @@ public final class BoundingBoxSize {
return 1.5f;
} else if (packetEntity instanceof PacketEntityGuardian packetEntityGuardian) { // TODO: 2.35 * guardian?
return packetEntityGuardian.isElder ? 1.9975f : 0.85f;
} else if (EntityTypes.END_CRYSTAL.equals(type)) {
} else if (type == EntityTypes.END_CRYSTAL) {
return 2f;
} else if (EntityTypes.ENDER_DRAGON.equals(type)) {
} else if (type == EntityTypes.ENDER_DRAGON) {
return 16f;
} else if (EntityTypes.FIREBALL.equals(type)) {
} else if (type == EntityTypes.FIREBALL) {
return 1f;
} else if (EntityTypes.GHAST.equals(type)) {
} else if (type == EntityTypes.GHAST) {
return 4f;
} else if (EntityTypes.GIANT.equals(type)) {
} else if (type == EntityTypes.GIANT) {
return 3.6f;
} else if (EntityTypes.GUARDIAN.equals(type)) {
} else if (type == EntityTypes.GUARDIAN) {
return 0.85f;
} else if (EntityTypes.IRON_GOLEM.equals(type)) {
} else if (type == EntityTypes.IRON_GOLEM) {
return 1.4f;
} else if (EntityTypes.MAGMA_CUBE.equals(type)) {
} else if (type == EntityTypes.MAGMA_CUBE) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
float size = sizeable.size;
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)
@@ -87,15 +87,15 @@ public final class BoundingBoxSize {
return 0.98f;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.MINECART_ABSTRACT)) {
return 0.98f;
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
} else if (type == EntityTypes.PLAYER || type == EntityTypes.MANNEQUIN) {
return 0.6f;
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
} else if (type == EntityTypes.POLAR_BEAR) {
return 1.4f;
} else if (EntityTypes.RAVAGER.equals(type)) {
} else if (type == EntityTypes.RAVAGER) {
return 1.95f;
} else if (EntityTypes.SHULKER.equals(type)) {
} else if (type == EntityTypes.SHULKER) {
return 1f;
} else if (EntityTypes.SLIME.equals(type)) {
} else if (type == EntityTypes.SLIME) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
float size = sizeable.size;
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)
@@ -104,27 +104,27 @@ public final class BoundingBoxSize {
}
return 0.3125f;
} else if (EntityTypes.SMALL_FIREBALL.equals(type)) {
} else if (type == EntityTypes.SMALL_FIREBALL) {
return 0.3125f;
} else if (EntityTypes.SPIDER.equals(type)) {
} else if (type == EntityTypes.SPIDER) {
return 1.4f;
} else if (EntityTypes.SQUID.equals(type)) {
} else if (type == EntityTypes.SQUID) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.8f : 0.95f;
} else if (EntityTypes.TURTLE.equals(type)) {
} else if (type == EntityTypes.TURTLE) {
return 1.2f;
} else if (EntityTypes.ALLAY.equals(type)) {
} else if (type == EntityTypes.ALLAY) {
return 0.35f;
} else if (EntityTypes.SNIFFER.equals(type)) {
} else if (type == EntityTypes.SNIFFER) {
return 1.9f;
} else if (EntityTypes.CAMEL.equals(type)) {
} else if (type == EntityTypes.CAMEL) {
return 1.7f;
} else if (EntityTypes.WIND_CHARGE.equals(type)) {
} else if (type == EntityTypes.WIND_CHARGE) {
return 0.3125f;
} else if (EntityTypes.ARMOR_STAND.equals(type)) {
} else if (type == EntityTypes.ARMOR_STAND) {
return 0.5F;
} else if (EntityTypes.FALLING_BLOCK.equals(type)) {
} else if (type == EntityTypes.FALLING_BLOCK) {
return 0.98F;
} else if (EntityTypes.FIREWORK_ROCKET.equals(type)) {
} else if (type == EntityTypes.FIREWORK_ROCKET) {
return 0.25F;
}
return 0.6f;
@@ -191,15 +191,15 @@ public final class BoundingBoxSize {
public static double getMyRidingOffset(PacketEntity packetEntity) {
final EntityType type = packetEntity.type;
if (EntityTypes.PIGLIN.equals(type) || EntityTypes.ZOMBIFIED_PIGLIN.equals(type) || EntityTypes.ZOMBIE.equals(type)) {
if (type == EntityTypes.PIGLIN || type == EntityTypes.ZOMBIFIED_PIGLIN || type == EntityTypes.ZOMBIE) {
return packetEntity.isBaby ? -0.05 : -0.45;
} else if (EntityTypes.SKELETON.equals(type)) {
} else if (type == EntityTypes.SKELETON) {
return -0.6;
} else if (EntityTypes.ENDERMITE.equals(type) || EntityTypes.SILVERFISH.equals(type)) {
} else if (type == EntityTypes.ENDERMITE || type == EntityTypes.SILVERFISH) {
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)) {
} else if (type == EntityTypes.EVOKER || type == EntityTypes.ILLUSIONER || type == EntityTypes.PILLAGER || type == EntityTypes.RAVAGER || type == EntityTypes.VINDICATOR || type == EntityTypes.WITCH) {
return -0.45;
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
} else if (type == EntityTypes.PLAYER || type == EntityTypes.MANNEQUIN) {
return -0.35;
}
@@ -219,21 +219,21 @@ public final class BoundingBoxSize {
return 0;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.BOAT)) {
return -0.1;
} else if (EntityTypes.HAPPY_GHAST.equals(type)) {
} else if (type == EntityTypes.HAPPY_GHAST) {
return 0.5;
} else if (EntityTypes.HOGLIN.equals(type) || EntityTypes.ZOGLIN.equals(type)) {
} else if (type == EntityTypes.HOGLIN || type == EntityTypes.ZOGLIN) {
return getHeight(player, packetEntity) - (packetEntity.isBaby ? 0.2 : 0.15);
} else if (EntityTypes.LLAMA.equals(type)) {
} else if (type == EntityTypes.LLAMA) {
return getHeight(player, packetEntity) * 0.67;
} else if (EntityTypes.PIGLIN.equals(type)) {
} else if (type == EntityTypes.PIGLIN) {
return getHeight(player, packetEntity) * 0.92;
} else if (EntityTypes.RAVAGER.equals(type)) {
} else if (type == EntityTypes.RAVAGER) {
return 2.1;
} else if (EntityTypes.SKELETON.equals(type)) {
} else if (type == EntityTypes.SKELETON) {
return (getHeight(player, packetEntity) * 0.75) - 0.1875;
} else if (EntityTypes.SPIDER.equals(type)) {
} else if (type == EntityTypes.SPIDER) {
return getHeight(player, packetEntity) * 0.5;
} else if (EntityTypes.STRIDER.equals(type)) {// depends on animation position, good luck getting it exactly, this is the best you can do though
} else if (type == EntityTypes.STRIDER) {// depends on animation position, good luck getting it exactly, this is the best you can do though
return getHeight(player, packetEntity) - 0.19;
}
return getHeight(player, packetEntity) * 0.75;
@@ -241,77 +241,77 @@ public final class BoundingBoxSize {
private static float getHeightMinusBaby(GrimPlayer player, PacketEntity packetEntity) {
final EntityType type = packetEntity.type;
if (EntityTypes.ARMADILLO.equals(type)) {
if (type == EntityTypes.ARMADILLO) {
return 0.65f;
} else if (EntityTypes.AXOLOTL.equals(type)) {
} else if (type == EntityTypes.AXOLOTL) {
return 0.42f;
} else if (EntityTypes.BEE.equals(type) || EntityTypes.DOLPHIN.equals(type) || EntityTypes.ALLAY.equals(type)) {
} else if (type == EntityTypes.BEE || type == EntityTypes.DOLPHIN || type == EntityTypes.ALLAY) {
return 0.6f;
} else if (EntityTypes.EVOKER_FANGS.equals(type) || EntityTypes.VEX.equals(type)) {
} else if (type == EntityTypes.EVOKER_FANGS || type == EntityTypes.VEX) {
return 0.8f;
} else if (EntityTypes.SQUID.equals(type)) {
} else if (type == EntityTypes.SQUID) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.8f : 0.95f;
} else if (EntityTypes.PARROT.equals(type) || EntityTypes.BAT.equals(type) || EntityTypes.PIG.equals(type) || EntityTypes.SPIDER.equals(type)) {
} else if (type == EntityTypes.PARROT || type == EntityTypes.BAT || type == EntityTypes.PIG || type == EntityTypes.SPIDER) {
return 0.9f;
} else if (EntityTypes.WITHER_SKULL.equals(type) || EntityTypes.SHULKER_BULLET.equals(type)) {
} else if (type == EntityTypes.WITHER_SKULL || type == EntityTypes.SHULKER_BULLET) {
return 0.3125f;
} else if (EntityTypes.BLAZE.equals(type)) {
} else if (type == EntityTypes.BLAZE) {
return 1.8f;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.BOAT)) {
// WHY DOES VIAVERSION OFFSET BOATS? THIS MAKES IT HARD TO SUPPORT, EVEN IF WE INTERPOLATE RIGHT.
// I gave up and just exempted boats from the reach check and gave up with interpolation for collisions
return 0.5625f;
} else if (EntityTypes.HAPPY_GHAST.equals(type)) {
} else if (type == EntityTypes.HAPPY_GHAST) {
return 4.0f;
} else if (EntityTypes.CAT.equals(type)) {
} else if (type == EntityTypes.CAT) {
return 0.7f;
} else if (EntityTypes.CAVE_SPIDER.equals(type)) {
} else if (type == EntityTypes.CAVE_SPIDER) {
return 0.5f;
} else if (EntityTypes.FROG.equals(type)) {
} else if (type == EntityTypes.FROG) {
return 0.55f;
} else if (EntityTypes.CHICKEN.equals(type)) {
} else if (type == EntityTypes.CHICKEN) {
return 0.7f;
} else if (EntityTypes.HOGLIN.equals(type) || EntityTypes.ZOGLIN.equals(type)) {
} else if (type == EntityTypes.HOGLIN || type == EntityTypes.ZOGLIN) {
return 1.4f;
} else if (EntityTypes.COW.equals(type)) {
} else if (type == EntityTypes.COW) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 1.4f : 1.3f;
} else if (EntityTypes.STRIDER.equals(type)) {
} else if (type == EntityTypes.STRIDER) {
return 1.7f;
} else if (EntityTypes.CREEPER.equals(type)) {
} else if (type == EntityTypes.CREEPER) {
return 1.7f;
} else if (EntityTypes.DONKEY.equals(type)) {
} else if (type == EntityTypes.DONKEY) {
return 1.5f;
} else if (packetEntity instanceof PacketEntityGuardian packetEntityGuardian) { // TODO: 2.35 * guardian?
return packetEntityGuardian.isElder ? 1.9975f : 0.85f;
} else if (EntityTypes.ENDERMAN.equals(type) || EntityTypes.WARDEN.equals(type)) {
} else if (type == EntityTypes.ENDERMAN || type == EntityTypes.WARDEN) {
return 2.9f;
} else if (EntityTypes.ENDERMITE.equals(type) || EntityTypes.COD.equals(type)) {
} else if (type == EntityTypes.ENDERMITE || type == EntityTypes.COD) {
return 0.3f;
} else if (EntityTypes.END_CRYSTAL.equals(type)) {
} else if (type == EntityTypes.END_CRYSTAL) {
return 2f;
} else if (EntityTypes.ENDER_DRAGON.equals(type)) {
} else if (type == EntityTypes.ENDER_DRAGON) {
return 8f;
} else if (EntityTypes.FIREBALL.equals(type)) {
} else if (type == EntityTypes.FIREBALL) {
return 1f;
} else if (EntityTypes.FOX.equals(type)) {
} else if (type == EntityTypes.FOX) {
return 0.7f;
} else if (EntityTypes.GHAST.equals(type)) {
} else if (type == EntityTypes.GHAST) {
return 4f;
} else if (EntityTypes.GIANT.equals(type)) {
} else if (type == EntityTypes.GIANT) {
return 12f;
} else if (EntityTypes.GUARDIAN.equals(type)) {
} else if (type == EntityTypes.GUARDIAN) {
return 0.85f;
} else if (EntityTypes.HORSE.equals(type)) {
} else if (type == EntityTypes.HORSE) {
return 1.6f;
} else if (EntityTypes.IRON_GOLEM.equals(type)) {
} else if (type == EntityTypes.IRON_GOLEM) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 2.7f : 2.9f;
} else if (EntityTypes.CREAKING.equals(type)) {
} else if (type == EntityTypes.CREAKING) {
return 2.7f;
} else if (EntityTypes.LLAMA.equals(type) || EntityTypes.TRADER_LLAMA.equals(type)) {
} else if (type == EntityTypes.LLAMA || type == EntityTypes.TRADER_LLAMA) {
return 1.87f;
} else if (EntityTypes.TROPICAL_FISH.equals(type)) {
} else if (type == EntityTypes.TROPICAL_FISH) {
return 0.4f;
} else if (EntityTypes.MAGMA_CUBE.equals(type)) {
} else if (type == EntityTypes.MAGMA_CUBE) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
float size = sizeable.size;
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)
@@ -323,43 +323,43 @@ public final class BoundingBoxSize {
return 0.7f;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.MINECART_ABSTRACT)) {
return 0.7f;
} else if (EntityTypes.MULE.equals(type)) {
} else if (type == EntityTypes.MULE) {
return 1.6f;
} else if (EntityTypes.MOOSHROOM.equals(type)) {
} else if (type == EntityTypes.MOOSHROOM) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 1.4f : 1.3f;
} else if (EntityTypes.OCELOT.equals(type)) {
} else if (type == EntityTypes.OCELOT) {
return 0.7f;
} else if (EntityTypes.PANDA.equals(type)) {
} else if (type == EntityTypes.PANDA) {
return 1.25f;
} else if (EntityTypes.PHANTOM.equals(type)) {
} else if (type == EntityTypes.PHANTOM) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
return 0.5f + sizeable.size * 0.1f;
}
return 1.8f;
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
} else if (type == EntityTypes.PLAYER || type == EntityTypes.MANNEQUIN) {
return 1.8f;
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
} else if (type == EntityTypes.POLAR_BEAR) {
return 1.4f;
} else if (EntityTypes.PUFFERFISH.equals(type)) {
} else if (type == EntityTypes.PUFFERFISH) {
return 0.7f;
} else if (EntityTypes.RABBIT.equals(type)) {
} else if (type == EntityTypes.RABBIT) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.5f : 0.7f;
} else if (EntityTypes.RAVAGER.equals(type)) {
} else if (type == EntityTypes.RAVAGER) {
return 2.2f;
} else if (EntityTypes.SALMON.equals(type)) {
} else if (type == EntityTypes.SALMON) {
return 0.4f;
} else if (EntityTypes.SHEEP.equals(type) || EntityTypes.GOAT.equals(type)) {
} else if (type == EntityTypes.SHEEP || type == EntityTypes.GOAT) {
return 1.3f;
} else if (EntityTypes.SHULKER.equals(type)) { // Could maybe guess peek size, although seems useless
} else if (type == EntityTypes.SHULKER) { // Could maybe guess peek size, although seems useless
return 2f;
} else if (EntityTypes.SILVERFISH.equals(type)) {
} else if (type == EntityTypes.SILVERFISH) {
return 0.3f;
} else if (EntityTypes.SKELETON.equals(type)) {
} else if (type == EntityTypes.SKELETON) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 1.99f : 1.95f;
} else if (EntityTypes.SKELETON_HORSE.equals(type)) {
} else if (type == EntityTypes.SKELETON_HORSE) {
return 1.6f;
} else if (EntityTypes.SLIME.equals(type)) {
} else if (type == EntityTypes.SLIME) {
if (packetEntity instanceof PacketEntitySizeable sizeable) {
float size = sizeable.size;
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)
@@ -369,43 +369,43 @@ public final class BoundingBoxSize {
}
return 0.3125f;
} else if (EntityTypes.SMALL_FIREBALL.equals(type)) {
} else if (type == EntityTypes.SMALL_FIREBALL) {
return 0.3125f;
} else if (EntityTypes.SNOW_GOLEM.equals(type)) {
} else if (type == EntityTypes.SNOW_GOLEM) {
return 1.9f;
} else if (EntityTypes.STRAY.equals(type)) {
} else if (type == EntityTypes.STRAY) {
return 1.99f;
} else if (EntityTypes.TURTLE.equals(type)) {
} else if (type == EntityTypes.TURTLE) {
return 0.4f;
} else if (EntityTypes.WITHER.equals(type)) {
} else if (type == EntityTypes.WITHER) {
return 3.5f;
} else if (EntityTypes.WITHER_SKELETON.equals(type)) {
} else if (type == EntityTypes.WITHER_SKELETON) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 2.4f : 2.535f;
} else if (EntityTypes.WOLF.equals(type)) {
} else if (type == EntityTypes.WOLF) {
return player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.85f : 0.8f;
} else if (EntityTypes.ZOMBIE_HORSE.equals(type)) {
} else if (type == EntityTypes.ZOMBIE_HORSE) {
return 1.6f;
} else if (EntityTypes.TADPOLE.equals(type)) {
} else if (type == EntityTypes.TADPOLE) {
return 0.3f;
} else if (EntityTypes.SNIFFER.equals(type)) {
} else if (type == EntityTypes.SNIFFER) {
return 1.75f;
} else if (EntityTypes.CAMEL.equals(type)) {
} else if (type == EntityTypes.CAMEL) {
return 2.375f;
} else if (EntityTypes.BREEZE.equals(type)) {
} else if (type == EntityTypes.BREEZE) {
return 1.77f;
} else if (EntityTypes.BOGGED.equals(type)) {
} else if (type == EntityTypes.BOGGED) {
return 1.99f;
} else if (EntityTypes.WIND_CHARGE.equals(type)) {
} else if (type == EntityTypes.WIND_CHARGE) {
return 0.3125f;
} else if (EntityTypes.ARMOR_STAND.equals(type)) {
} else if (type == EntityTypes.ARMOR_STAND) {
return 1.975F;
} else if (EntityTypes.FALLING_BLOCK.equals(type)) {
} else if (type == EntityTypes.FALLING_BLOCK) {
return 0.98F;
} else if (EntityTypes.VILLAGER.equals(type) && player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
} else if (type == EntityTypes.VILLAGER && player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
return 1.8F;
} else if (EntityTypes.FIREWORK_ROCKET.equals(type)) {
} else if (type == EntityTypes.FIREWORK_ROCKET) {
return 0.25F;
} else if (EntityTypes.COPPER_GOLEM.equals(type)) {
} else if (type == EntityTypes.COPPER_GOLEM) {
return 1.0F;
}
return 1.95f;
@@ -15,8 +15,6 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.tags.SyncedTags;
import ac.grim.grimac.utils.function.BlockStateAtPosPredicate;
import ac.grim.grimac.utils.function.Int3Consumer;
import ac.grim.grimac.utils.latency.CompensatedWorld;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
@@ -44,7 +42,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@UtilityClass
@@ -148,7 +145,7 @@ public final class Collisions {
for (float stepHeight : stepHeights) {
Vector3dm vec3d2 = collideBoundingBoxLegacy(new Vector3dm(desiredX, stepHeight, desiredZ), startingOffsetBox, stepCollisions, order);
if (getHorizontalDistanceSqr(vec3d2.getX(), vec3d2.getZ()) > getHorizontalDistanceSqr(collisionResult.getX(), collisionResult.getZ())) {
if (getHorizontalDistanceSqr(vec3d2) > getHorizontalDistanceSqr(collisionResult)) {
final double d = player.boundingBox.minY - startingOffsetBox.minY;
collisionResult = vec3d2.add(new Vector3dm(0.0, -d, 0.0));
break;
@@ -162,13 +159,13 @@ public final class Collisions {
Vector3dm stepUpBugFix = collideBoundingBoxLegacy(new Vector3dm(0, stepUpHeight, 0), player.boundingBox.copy().expandToCoordinate(desiredX, 0, desiredZ), desiredMovementCollisionBoxes, order);
if (stepUpBugFix.getY() < stepUpHeight) {
Vector3dm stepUpBugFixResult = collideBoundingBoxLegacy(new Vector3dm(desiredX, 0, desiredZ), player.boundingBox.copy().offset(0, stepUpBugFix.getY(), 0), desiredMovementCollisionBoxes, order).add(stepUpBugFix);
if (getHorizontalDistanceSqr(stepUpBugFixResult.getX(), stepUpBugFixResult.getZ()) > getHorizontalDistanceSqr(regularStepUp.getX(), regularStepUp.getZ())) {
if (getHorizontalDistanceSqr(stepUpBugFixResult) > getHorizontalDistanceSqr(regularStepUp)) {
regularStepUp = stepUpBugFixResult;
}
}
}
if (getHorizontalDistanceSqr(regularStepUp.getX(), regularStepUp.getZ()) > getHorizontalDistanceSqr(collisionResult.getX(), collisionResult.getZ())) {
if (getHorizontalDistanceSqr(regularStepUp) > getHorizontalDistanceSqr(collisionResult)) {
collisionResult = regularStepUp.add(collideBoundingBoxLegacy(new Vector3dm(0, -regularStepUp.getY() + (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? desiredY : 0), 0), player.boundingBox.copy().offset(regularStepUp.getX(), regularStepUp.getY(), regularStepUp.getZ()), desiredMovementCollisionBoxes, order));
}
}
@@ -388,8 +385,8 @@ public final class Collisions {
return !getCollisionBoxes(player, playerBB, null, true);
}
public static double getHorizontalDistanceSqr(double x, double z) {
return x * x + z * z;
public static double getHorizontalDistanceSqr(Vector3dm vector) {
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
}
public static Vector3dm maybeBackOffFromEdge(Vector3dm vec3, GrimPlayer player, boolean overrideVersion) {
@@ -520,17 +517,17 @@ public final class Collisions {
if (blockAbove.getType().isAir()) {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
if (block.isDrag()) {
vector.setY(Math.max(-0.9D, vector.vectorY - 0.03D));
vector.vector.setY(Math.max(-0.9D, vector.vector.getY() - 0.03D));
} else {
vector.setY(Math.min(1.8D, vector.vectorY + 0.1D));
vector.vector.setY(Math.min(1.8D, vector.vector.getY() + 0.1D));
}
}
} else {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
if (block.isDrag()) {
vector.setY(Math.max(-0.3D, vector.vectorY - 0.03D));
vector.vector.setY(Math.max(-0.3D, vector.vector.getY() - 0.03D));
} else {
vector.setY(Math.min(0.7D, vector.vectorY + 0.06D));
vector.vector.setY(Math.min(0.7D, vector.vector.getY() + 0.06D));
}
}
}
@@ -745,13 +742,8 @@ public final class Collisions {
return box.isFullBlock();
}
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, Predicate<WrappedBlockState> searchingFor) {
return hasMaterial(player, checkBox, (state, x, y, z) -> searchingFor.test(state));
}
// Thanks Tuinity
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, BlockStateAtPosPredicate searchingFor) {
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, Predicate<Pair<WrappedBlockState, Vector3d>> searchingFor) {
int minBlockX = (int) Math.floor(checkBox.minX);
int maxBlockX = (int) Math.floor(checkBox.maxX);
int minBlockY = (int) Math.floor(checkBox.minY);
@@ -805,7 +797,7 @@ public final class Collisions {
WrappedBlockState data = section.get(CompensatedWorld.blockVersion, x & 0xF, y & 0xF, z & 0xF, false);
if (searchingFor.test(data, x, y, z))
if (searchingFor.test(new Pair<>(data, new Vector3d(x, y, z))))
return true;
}
}
@@ -816,7 +808,7 @@ public final class Collisions {
}
// Thanks Tuinity
public static void forEachCollisionBox(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox checkBox, @NotNull Int3Consumer searchingFor) {
public static void forEachCollisionBox(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox checkBox, @NotNull Consumer<@NotNull Vector3d> searchingFor) {
int minBlockX = (int) Math.floor(checkBox.minX - COLLISION_EPSILON) - 1;
int maxBlockX = (int) Math.floor(checkBox.maxX + COLLISION_EPSILON) + 1;
int minBlockY = (int) Math.floor(checkBox.minY - COLLISION_EPSILON) - 1;
@@ -884,7 +876,7 @@ public final class Collisions {
final CollisionBox collisionBox = CollisionData.getData(type).getMovementCollisionBox(player, player.getClientVersion(), data, x, y, z);
if (collisionBox.isIntersected(checkBox)) {
searchingFor.accept(x, y, z);
searchingFor.accept(new Vector3d(x, y, z));
}
}
}
@@ -8,9 +8,10 @@ import org.jetbrains.annotations.NotNull;
@UtilityClass
public class FluidFallingAdjustedMovement {
public static double getAdjustedY(GrimPlayer player, double gravity, boolean isFalling, double currentY) {
if (!player.hasGravity || player.isSprinting) return currentY;
isFalling = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? isFalling : currentY < 0;
return isFalling && Math.abs(currentY - 0.005) >= 0.003 && Math.abs(currentY - gravity / 16.0) < 0.003 ? -0.003 : currentY - gravity / 16.0;
public static Vector3dm getFluidFallingAdjustedMovement(@NotNull GrimPlayer player, double gravity, boolean isFalling, Vector3dm velocity) {
if (!player.hasGravity || player.isSprinting) return velocity;
isFalling = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? isFalling : velocity.getY() < 0;
double newY = isFalling && Math.abs(velocity.getY() - 0.005) >= 0.003 && Math.abs(velocity.getY() - gravity / 16.0) < 0.003 ? -0.003 : velocity.getY() - gravity / 16.0;
return new Vector3dm(velocity.getX(), newY, velocity.getZ());
}
}
Loaded 100 of 186 files, more files were not shown because too many files have changed in this diff. Show more