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
386 changed files with 7318 additions and 7011 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
.gitea/CODEOWNERS linguist-detectable=false
-3
View File
@@ -1,3 +0,0 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+30 -53
View File
@@ -3,11 +3,6 @@ name: Reusable Build and Publish
on:
workflow_call:
inputs:
runner:
description: 'The runner to use for the build job.'
required: false
type: string
default: 'ubuntu-latest'
is-release-build:
description: 'True if this is a release build, which affects versioning and publishing.'
required: true
@@ -41,10 +36,9 @@ on:
jobs:
build:
runs-on: ${{ inputs.runner }}
runs-on: tenki-standard-autoscale
permissions:
# Grant permission to write contents (for creating tags/refs)
contents: write
contents: read
packages: write
env:
@@ -129,14 +123,14 @@ jobs:
fi
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: grimac-bukkit
path: ${{ github.workspace }}/bukkit/build/libs/grimac-bukkit-${{ env.BUILD_VERSION }}.jar
if-no-files-found: error
- name: Upload Fabric Artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: grimac-fabric
path: ${{ github.workspace }}/fabric/build/libs/grimac-fabric-${{ env.BUILD_VERSION }}.jar
@@ -183,12 +177,23 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Generate build number
if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) && inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != ''
id: buildnumber
uses: onyxmueller/build-tag-number@v1
- name: Publish Fabric to Modrinth
if: ${{ inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != '' }}
uses: Kir-Antipov/mc-publish@v3.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
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
@@ -202,52 +207,24 @@ jobs:
modrinth-featured: ${{ inputs.is-release-build }} # Only feature release builds
files: bukkit/build/libs/grimac-bukkit-${{ env.BUILD_VERSION }}.jar
name: LightningGrim Anticheat (Bukkit) ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
version: ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
name: Grim Anticheat (Bukkit) ${{ env.BUILD_VERSION }}
version: ${{ env.BUILD_VERSION }}
version-type: ${{ inputs.release-version-type }}
changelog: ${{ steps.changelog.outputs.notes }}
loaders: |
bukkit
spigot
paper
folia
purpur
game-versions: '>=1.7 <=1.21.11'
- 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: LightningGrim Anticheat (Fabric) ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
version: ${{ env.BUILD_VERSION }}-b${{ steps.buildnumber.outputs.build_number }}
version-type: ${{ inputs.release-version-type }}
changelog: ${{ steps.changelog.outputs.notes }}
loaders: |
fabric
game-versions: '>=1.16.1 <=1.21.10'
dependencies: 'fabric-api'
bukkit
spigot
paper
folia
purpur
game-versions: '>=1.7 <=1.21.10'
- name: Publish to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' && inputs.is-release-build == false }}
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' }}
run: |
curl -X POST \
-H "Content-Type: application/json" \
-H "User-Agent: GrimACBuilder/v1" \
-H "Authorization: Bearer ${{ secrets.BUILD_UPDATE_TOKEN }}" \
-d '{"org": "${{ github.repository_owner }}", "repository": "${{ github.event.repository.name }}", "branch": "${{ github.ref_name }}", "version": "${{ env.BUILD_VERSION }}"}' \
"${{ secrets.BUILD_UPDATE_URL }}"
- name: Publish Release to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' && inputs.is-release-build == true }}
run: |
curl -X POST \
-H "Content-Type: application/json" \
-H "User-Agent: GrimACBuilder/v1" \
-H "Authorization: Bearer ${{ secrets.BUILD_UPDATE_TOKEN }}" \
-d '{"org": "${{ github.repository_owner }}", "repository": "${{ github.event.repository.name }}", "branch": "release", "version": "${{ env.BUILD_VERSION }}"}' \
"${{ secrets.BUILD_UPDATE_URL }}"
"${{ secrets.BUILD_UPDATE_URL }}"
+50 -50
View File
@@ -12,64 +12,64 @@
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [dev]
workflow_dispatch:
push:
branches: [dev]
jobs:
analyze:
name: Analyze
runs-on: tenki-standard-autoscale
permissions:
actions: read
contents: read
security-events: write
analyze:
name: Analyze
runs-on: tenki-standard-autoscale
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java-kotlin']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
strategy:
fail-fast: false
matrix:
language: ['java-kotlin']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v5
steps:
- name: Checkout repository
uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
#- run: |
# make bootstrap
# make release
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
-7
View File
@@ -12,11 +12,6 @@ on:
description: 'Upload to Modrinth & API? (Uncheck for a dry run)'
type: boolean
default: true
runner:
description: 'The runner to use for the build job.'
required: false
type: string
default: 'tenki-standard-autoscale'
push:
branches:
- 'main'
@@ -24,7 +19,6 @@ on:
- 'dev'
- 'release'
- 'release/**'
- 'lightning'
paths:
- 'bukkit/**'
- 'common/**'
@@ -37,7 +31,6 @@ jobs:
call-build-workflow:
uses: ./.github/workflows/build-and-publish.yml
with:
runner: ${{ github.event.inputs.runner || 'tenki-standard-autoscale' }}
is-release-build: false
release-version-type: alpha # Dev builds are always 'alpha'
run-build-scan: ${{ github.event.inputs.run-build-scan == 'true' }}
-1
View File
@@ -8,7 +8,6 @@ jobs:
call-release-workflow:
uses: ./.github/workflows/build-and-publish.yml
with:
runner: 'tenki-standard-autoscale'
is-release-build: true
release-version-type: release # Official releases are 'release'
# Use the body of the GitHub Release as the changelog
-2
View File
@@ -73,5 +73,3 @@ nb-configuration.xml
##############################
run/
fabric/run/
.ignored/
+3 -3
View File
@@ -1,6 +1,6 @@
# Contributing to LightningGrim
# Contributing to GrimAC
Thank you for your interest in contributing to LightningGrim. This document outlines the guidelines for
Thank you for your interest in contributing to GrimAC. This document outlines the guidelines for
making pull
requests to the project. *We're usually pretty lenient with pull requests, but this guide will help
make the process go more smoothly.*
@@ -31,7 +31,7 @@ make the process go more smoothly.*
### Development Notes
- LightningGrim is built using [Gradle](https://gradle.org/) kotlin scripts.
- GrimAC is built using [Gradle](https://gradle.org/) kotlin scripts.
- Java 21 is currently required to build the project. A minimum of Java 17 is required to run it.
### Questions & Support
-96
View File
@@ -1,96 +0,0 @@
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/8.14.3/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build
> Configure project :buildSrc
WARNING: Unsupported Kotlin plugin version.
The `embedded-kotlin` and `kotlin-dsl` plugins rely on features of Kotlin `2.0.21` that might work differently than in the requested version `2.2.20`.
> Task :buildSrc:checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :buildSrc:generateExternalPluginSpecBuilders UP-TO-DATE
> Task :buildSrc:extractPrecompiledScriptPluginPlugins UP-TO-DATE
> Task :buildSrc:compilePluginsBlocks UP-TO-DATE
> Task :buildSrc:generatePrecompiledScriptPluginAccessors UP-TO-DATE
> Task :buildSrc:generateScriptPluginAdapters UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:compileKotlin
w: Language version 1.8 is deprecated and its support will be removed in a future version of Kotlin
> Task :buildSrc:compileJava NO-SOURCE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar
> Configure project :
Build configuration:
shadePE = false
relocate = true
mavenLocalOverride = true
release = false
version = 2.3.73-HEAD-bebcab7+lite
> Task :clean
> Task :bukkit:clean
> Task :common:clean
> Task :assemble UP-TO-DATE
> Task :check UP-TO-DATE
> Task :bukkit:generateBukkitPluginDescription
> Task :bukkit:processResources
> Task :bukkit:generateTestEffectiveLombokConfig
> Task :common:processResources
> Task :bukkit:sourcesJar
> Task :spotlessInternalRegisterDependencies
> Task :bukkit:spotlessJava
> Task :bukkit:spotlessJavaApply
> Task :bukkit:spotlessJavaCheck
> Task :bukkit:spotlessKotlinGradle
> Task :bukkit:spotlessKotlinGradleApply
> Task :bukkit:spotlessKotlinGradleCheck
> Task :bukkit:spotlessCheck
> Task :bukkit:processTestResources NO-SOURCE
> Task :bukkit:spotlessApply
> Task :common:sourcesJar
> Task :common:spotlessJava
> Task :common:spotlessJavaApply
> Task :common:spotlessJavaCheck
> Task :common:spotlessKotlinGradle
> Task :common:spotlessKotlinGradleApply
> Task :common:spotlessKotlinGradleCheck
> Task :common:spotlessCheck
> Task :common:generateTestEffectiveLombokConfig
> Task :common:processTestResources NO-SOURCE
> Task :common:spotlessApply
> Task :common:eclipseClasspath
Could not resolve: com.github.retrooper:packetevents-api:2.11.2-SNAPSHOT
> Task :common:eclipseJdt
> Task :common:eclipseProject
> Task :common:eclipse
> Task :bukkit:generateEffectiveLombokConfig
> Task :common:generateEffectiveLombokConfig
> Task :common:compileJava FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':common:compileJava'.
> Could not resolve all files for configuration ':common:compileClasspath'.
> Could not find com.github.retrooper:packetevents-api:2.11.2-SNAPSHOT.
Searched in the following locations:
- https://repo.grim.ac/snapshots/com/github/retrooper/packetevents-api/2.11.2-SNAPSHOT/maven-metadata.xml
- https://repo.grim.ac/snapshots/com/github/retrooper/packetevents-api/2.11.2-SNAPSHOT/packetevents-api-2.11.2-SNAPSHOT.pom
- file:/home/admin/.m2/repository/com/github/retrooper/packetevents-api/2.11.2-SNAPSHOT/maven-metadata.xml
- file:/home/admin/.m2/repository/com/github/retrooper/packetevents-api/2.11.2-SNAPSHOT/packetevents-api-2.11.2-SNAPSHOT.pom
Required by:
project :common
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 17s
38 actionable tasks: 31 executed, 7 up-to-date
+16 -16
View File
@@ -1,5 +1,5 @@
<div align="center">
<h1>LightningGrim</h1>
<h1>GrimAC</h1>
<div>
<a href="https://github.com/GrimAnticheat/Grim/actions/workflows/gradle-publish.yml">
@@ -18,20 +18,20 @@
<br>
</div>
LightningGrim is an open source Minecraft anticheat fork designed to support the latest versions of Minecraft.
GrimAC is an open source Minecraft anticheat designed to support the latest versions of Minecraft.
It currently supports minecraft versions 1.8–1.21. Geyser players are fully exempt from the anticheat to prevent false positives.
This project is considered feature-complete for the 2.0 (open-source) branch. If you would like a bug fix or enhancement and cannot sponsor the work, pull requests are welcome.
A premium version is planned, which will offer additional subscription-based paid checks, such as heuristics.
## Changes over GrimAC:
## Downloads
- WallHit - checks for hitting through walls
- EntityPierce - checks for hitting through other entities
- Bukkit Event Piston Optimizations
- Optimized Hitbox/Collision Boxes
- Optimized Reach calculations
- Checks for PacketOrder (patches lots of noslow, autoblocks, etc...)
- Checks for Inventory interactions (prevents using crafting slots as extra storage, moving while having inventory open, etc...)
- Latest updates:
- **[Modrinth](https://modrinth.com/plugin/grimac)** *(recommended)*
- GitHub
artifacts: [Bukkit](https://nightly.link/GrimAnticheat/Grim/workflows/gradle-publish/2.0/grimac-bukkit.zip), [Fabric](https://nightly.link/GrimAnticheat/Grim/workflows/gradle-publish/2.0/grimac-fabric.zip) *(bleeding edge)*
- Major releases only:
- ~~[Hangar](https://hangar.papermc.io/GrimAnticheat/GrimAnticheat)~~
- ~~[SpigotMC](https://www.spigotmc.org/resources/grim-anticheat.99923/)~~
## Requirements & Installation
@@ -56,20 +56,20 @@ are.
## Developer Plugin API
LightningGrim's plugin API allows you to integrate LightningGrim into your own plugins. Visit
Grim's plugin API allows you to integrate Grim into your own plugins. Visit
the [plugin API repository](https://github.com/GrimAnticheat/GrimAPI) for the source code and more
information.
## Compiling From Source
1. `git clone https://github.com/Axionize/LightningGrim`
2. `cd LightningGrim`
3. `./gradlew build clean eclipse --warning-mode all`
1. `git clone https://github.com/GrimAnticheat/Grim.git`
2. `cd Grim`
3. `./gradlew build`
4. The final jars will compile into the `<platform>/build/libs` folders
## LightningGrim Supremacy
## Grim Supremacy
What makes LightningGrim stand out against other anticheats?
What makes Grim stand out against other anticheats?
### Movement Simulation Engine
+2 -21
View File
@@ -2,19 +2,13 @@
* GrimAC Build Configuration
*
* Build Flags:
* -PshadePE=true - Bundles PacketEvents into the jar (default: external/lite).
* -PshadePE=true - Enables 'lite' mode
* -Prelocate=false - Adds 'no_relocate' modifier
* -Prelease=true - Removes commit/modifiers for release build
*
* Logic in: buildSrc/versioning/BuildConfig.kt & VersionUtil.kt
*/
plugins {
base
}
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.compile.JavaCompile
import versioning.BuildConfig
import versioning.VersionUtil
@@ -23,7 +17,7 @@ BuildConfig.init(project)
val baseVersion = "2.3.73"
group = "ac.grim.grimac"
version = VersionUtil.computeVersion(baseVersion)
description = "Libre simulation anticheat designed for 1.21 with 1.8–1.21 support, powered by PacketEvents 2.11.2."
description = "Libre simulation anticheat designed for 1.21 with 1.8–1.21 support, powered by PacketEvents 2.0."
ext["timestamp"] = System.currentTimeMillis().toString()
ext["git_branch"] = VersionUtil.getGitBranch(true)
@@ -53,16 +47,3 @@ subprojects {
options.isIncremental = true
}
}
tasks.register<Copy>("copyBukkitJarToRoot") {
dependsOn(":bukkit:shadowJar")
from(project(":bukkit").layout.buildDirectory.dir("libs")) {
include("LightningGrim-bukkit-$version.jar") // current build only; stale jars from prior commits would collide on rename
rename("LightningGrim-bukkit-.*\\.jar", "LightningGrim-$baseVersion.jar")
}
into(layout.buildDirectory.dir("libs"))
}
tasks.named("build") {
dependsOn("copyBukkitJarToRoot")
}
+1 -1
View File
@@ -1,6 +1,6 @@
plugins {
`kotlin-dsl`
kotlin("plugin.serialization") version "2.2.20"
kotlin("plugin.serialization") version "1.9.0" // Use the same Kotlin version as your main project
}
repositories {
-42
View File
@@ -1,42 +0,0 @@
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.artifacts.repositories.InclusiveRepositoryContentDescriptor
import org.gradle.kotlin.dsl.maven
import versioning.BuildConfig
/**
* Registers a repository with strict content exclusivity.
*
* Logic:
* 1. If BuildConfig.mavenLocalOverride is true, checks Maven Local first.
* 2. Checks the provided remote URL second.
* 3. Enforces strict filtering so Gradle never checks this repo for other deps,
* and never checks other repos (like Central) for these deps.
*/
fun RepositoryHandler.exclusive(
url: String,
repoConfig: MavenArtifactRepository.() -> Unit = {},
filterConfig: InclusiveRepositoryContentDescriptor.() -> Unit
) {
// We define the repositories inside the function scope so they are added to the handler
val remote = maven(url, repoConfig)
exclusive(remote, filterConfig)
}
fun RepositoryHandler.exclusive(
remote: MavenArtifactRepository,
filterConfig: InclusiveRepositoryContentDescriptor.() -> Unit
) {
// We access BuildConfig here again to check for local override
val local = if (BuildConfig.mavenLocalOverride) mavenLocal() else null
exclusiveContent {
if (local != null) {
forRepositories(local, remote)
} else {
forRepositories(remote)
}
filter(filterConfig)
}
}
@@ -66,6 +66,7 @@ tasks {
listOf(
"bungee.yml",
"velocity-plugin.json",
"fabric.mod.json",
"grimac.properties"
)
) {
@@ -6,13 +6,7 @@ plugins {
}
tasks.named<ShadowJar>("shadowJar") {
minimize {
// The PostgreSQL JDBC driver is loaded reflectively via java.sql ServiceLoader,
// so minimize() sees no direct references and would strip it. Keep it whole.
exclude(dependency("org.postgresql:postgresql:.*"))
// Same for the SLF4J NOP provider, loaded via SLF4JServiceProvider ServiceLoader.
exclude(dependency("org.slf4j:slf4j-nop:.*"))
}
minimize()
archiveFileName = "${rootProject.name}-${project.name}-${rootProject.version}.jar"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
@@ -38,7 +32,6 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("org.incendo", "ac.grim.grimac.shaded.incendo")
relocate("io.leangen.geantyref", "ac.grim.grimac.shaded.geantyref") // Required by cloud
relocate("com.zaxxer", "ac.grim.grimac.shaded.zaxxer") // Database history
relocate("org.postgresql", "ac.grim.grimac.shaded.postgresql") // Database history (JDBC driver); service file rewritten by mergeServiceFiles
}
mergeServiceFiles()
}
@@ -38,7 +38,7 @@ import versioning.BuildConfig.shadePE
* ./gradlew build -DshadePE=true -Drelease=true
* ```
*
* @property shadePE Bundles PacketEvents into the jar when true; consumes an external provider when false (default; emits the `-lite` artifact).
* @property shadePE If true, shades PacketEvents into the jar. Default: true.
* @property relocate If true, relocates shaded dependencies to avoid conflicts. Default: true.
* @property release If true, omits commit hash and modifiers from version string. Default: false.
* @property mavenLocalOverride If true, will make artifacts in mavenLocal() will be used instead of their remote counterparts for this build. Default: false
@@ -53,10 +53,10 @@ object BuildConfig {
* ```
*/
fun init(project: Project) {
_shadePE = resolveBool(project, "shadePE", altKey = "SHADE_PE", default = false)
_shadePE = resolveBool(project, "shadePE", altKey = "SHADE_PE", default = true)
_relocate = resolveBool(project, "relocate", altKey = "RELOCATE_JAR", default = true)
_release = resolveBool(project, "release", default = false)
_mavenLocalOverride = resolveBool(project, "mavenLocalOverride", altKey = "MAVEN_LOCAL_OVERRIDE", default = false)
_mavenLocalOverride = resolveBool(project, "mavenLocalOverride", default = false)
}
// Unified resolution logic (System > Gradle > Env)
@@ -78,7 +78,7 @@ object BuildConfig {
private var _release: Boolean? = null
private var _mavenLocalOverride: Boolean? = null
/** If true, shades PacketEvents into the jar. Default: false (external provider). */
/** If true, shades PacketEvents into the jar. Default: true. */
val shadePE: Boolean get() = _shadePE
?: error("BuildConfig.shadePE accessed before init() was called")
@@ -3,7 +3,7 @@ package versioning
import java.io.ByteArrayOutputStream
/**
* Utility for computing the version string of LightningGrim artifacts.
* Utility for computing the version string of GrimAC artifacts.
*
* The version string is constructed based on:
* - A base semantic version (e.g., "2.3.72")
@@ -91,7 +91,6 @@ object VersionUtil {
val mainBranch = System.getenv("GRIM_MAIN_BRANCH") ?: "2.0"
return when (branch) {
"lightning" -> null
"main", mainBranch -> null // ← ignore these branches
else -> branch
}
+60 -40
View File
@@ -10,38 +10,73 @@ plugins {
}
repositories {
// 1. Fallback for non-exclusive deps (e.g. Maven Central deps)
if (BuildConfig.mavenLocalOverride) mavenLocal()
// 2. Exclusive Repositories (One HTTP request per dep)
exclusive("https://repo.papermc.io/repository/maven-public/", { name = "papermc" }) {
includeGroup("io.papermc.paper")
includeGroup("net.md-5")
if (BuildConfig.mavenLocalOverride) {
mavenLocal()
}
exclusive("https://libraries.minecraft.net", { mavenContent { releasesOnly() } }) {
includeModule("com.mojang", "brigadier")
// For paper-api
exclusiveContent {
forRepository {
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
}
filter {
includeGroup("io.papermc.paper")
includeGroup("net.md-5")
}
}
exclusive("https://repo.extendedclip.com/content/repositories/placeholderapi/") {
includeGroup("me.clip")
exclusiveContent {
forRepository {
maven("https://libraries.minecraft.net") { // Brigadier
mavenContent { releasesOnly() }
}
}
filter {
includeModule("com.mojang", "brigadier")
}
}
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
includeGroup("com.github.retrooper")
// For placeholderapi
exclusiveContent {
forRepository {
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
}
filter {
includeGroup("me.clip")
}
}
exclusive("https://repo.grim.ac/snapshots") {
includeGroup("ac.grim.grimac")
// For GrimAPI and PacketEvents (transitive from :common, direct in :bukkit)
exclusiveContent {
forRepository {
maven("https://repo.grim.ac/snapshots")
}
filter {
includeGroup("ac.grim.grimac")
includeGroup("com.github.retrooper")
}
}
exclusive("https://nexus.scarsz.me/content/repositories/releases", { mavenContent { releasesOnly() } }) {
includeGroup("github.scarsz")
// For Configuralize (transitive from :common)
exclusiveContent {
forRepository {
maven("https://nexus.scarsz.me/content/repositories/releases") {
mavenContent { releasesOnly() }
}
}
filter {
includeGroup("github.scarsz")
}
}
// Maven Central Fallback
mavenCentral()
}
dependencies {
compileOnly(libs.paper.api)
compileOnly(libs.placeholderapi)
@@ -52,24 +87,22 @@ dependencies {
compileOnly(libs.packetevents.spigot)
}
implementation(libs.cloud.paper)
implementation(libs.grim.bukkit.internal)
implementation("org.slf4j:slf4j-nop:2.0.17") // Provide SLF4J NOP provider to suppress missing-provider warning.
implementation("org.slf4j:slf4j-api:2.0.17") // Bundle a relocated SLF4J API for shaded dependencies.
implementation(libs.adventure.platform.bukkit)
implementation(project(":common"))
shadow(project(":common"))
}
bukkit {
name = "LightningGrim"
author = "LightningGrim"
name = "GrimAC"
author = "GrimAC"
main = "ac.grim.grimac.platform.bukkit.GrimACBukkitLoaderPlugin"
website = "https://grim.ac/"
apiVersion = "1.13"
foliaSupported = true
if (!BuildConfig.shadePE) {
// Bukkit `depend` is case-sensitive; canonical PE plugin name is PascalCase.
depend = listOf("PacketEvents")
depend = listOf("packetevents")
}
softDepend = listOf(
@@ -83,7 +116,6 @@ bukkit {
"floodgate",
"FastLogin",
"PlaceholderAPI",
"ZNPCsPlus",
)
permissions {
@@ -153,32 +185,20 @@ bukkit {
"Shows lists of specific data"
default = Permission.Default.FALSE
}
}
}
publishing.publications.create<MavenPublication>("maven") {
artifact(tasks.named("shadowJar"))
artifact(tasks["shadowJar"])
}
apply(from = "$projectDir/gradle/packetevents-provider.gradle.kts")
@Suppress("UNCHECKED_CAST")
val registerStagePE = project.extra["registerStagePacketEventsTask"]
as (org.gradle.api.Project, String, java.io.File) -> org.gradle.api.tasks.TaskProvider<Copy>
val stagePEJar = registerStagePE(project, "stageLocalPacketEvents", file("run"))
tasks {
runServer {
minecraftVersion("1.21.11")
// External mode stages the locally built True-OG PacketEvents JAR.
if (!BuildConfig.shadePE) {
dependsOn(stagePEJar)
}
minecraftVersion("1.21.10")
}
shadowJar {
mergeServiceFiles()
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
@@ -1,76 +0,0 @@
// True-OG PacketEvents JAR staging convention.
// Resolution order (first hit wins): -PtruogPacketEventsJar, TRUEOG_PACKETEVENTS_JAR env, default glob under ~/eclipse-workspace/PacketEvents/spigot/build/libs (case-insensitive, latest by mtime). See context/refs/packetevents-provider.md.
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.Comparator
fun resolveTrueOgPacketEventsJar(project: Project): File {
val home = System.getProperty("user.home")
val defaultDir = Paths.get(home, "eclipse-workspace", "PacketEvents", "spigot", "build", "libs")
// Every resolution path is reported in the failure message regardless of which got tried.
val propValue = (project.findProperty("truogPacketEventsJar") as? String)?.takeIf { it.isNotBlank() } ?: "<unset>"
val envValue = System.getenv("TRUEOG_PACKETEVENTS_JAR")?.takeIf { it.isNotBlank() } ?: "<unset>"
val attempts = listOf(
"Gradle property truogPacketEventsJar=$propValue",
"Environment variable TRUEOG_PACKETEVENTS_JAR=$envValue",
"Default glob $defaultDir/PacketEvents-*.jar (case-insensitive; falls back to packetevents-spigot-*.jar; latest by mtime)",
)
val gradleProp = (project.findProperty("truogPacketEventsJar") as? String)?.trim()
if (!gradleProp.isNullOrEmpty()) {
val f = File(gradleProp)
if (f.isFile) return f
}
val envVar = System.getenv("TRUEOG_PACKETEVENTS_JAR")?.trim()
if (!envVar.isNullOrEmpty()) {
val f = File(envVar)
if (f.isFile) return f
}
if (Files.isDirectory(defaultDir)) {
val nameRegex = Regex("(?i)(PacketEvents|packetevents-spigot|packetevents)-.*\\.jar")
val match = Files.list(defaultDir).use { stream ->
stream
.filter { nameRegex.matches(it.fileName.toString()) }
.sorted(Comparator.comparingLong<Path> { Files.getLastModifiedTime(it).toMillis() }.reversed())
.findFirst()
.orElse(null)
}
if (match != null) return match.toFile()
}
throw GradleException(
buildString {
appendLine("True-OG PacketEvents JAR not found. Tried:")
attempts.forEach { appendLine(" - $it") }
appendLine("Provide one of:")
appendLine(" ./gradlew -PtruogPacketEventsJar=/abs/path/to/PacketEvents.jar ...")
appendLine(" TRUEOG_PACKETEVENTS_JAR=/abs/path/to/PacketEvents.jar ./gradlew ...")
appendLine(" Build the fork at \${user.home}/eclipse-workspace/PacketEvents (default location).")
},
)
}
// Registers a Copy task that stages the resolved JAR into runDir/plugins/PacketEvents.jar. See context/refs/packetevents-provider.md for the consumer integration snippet.
val registerStagePacketEventsTask: (Project, String, File) -> TaskProvider<Copy> = { p, taskName, runDir ->
p.tasks.register(taskName, Copy::class.java) {
group = "packetevents"
description = "Stage the locally built True-OG PacketEvents JAR into plugins/."
// Eager filesystem resolution at configuration time is incompatible with the config cache.
notCompatibleWithConfigurationCache(
"Resolves the JAR via filesystem inspection during configuration.",
)
val jar = resolveTrueOgPacketEventsJar(p)
from(jar)
into(runDir.resolve("plugins"))
rename { "PacketEvents.jar" }
}
}
extra["resolveTrueOgPacketEventsJar"] = { p: Project -> resolveTrueOgPacketEventsJar(p) }
extra["registerStagePacketEventsTask"] = registerStagePacketEventsTask
@@ -1,7 +1,5 @@
package ac.grim.grimac.platform.bukkit;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.Platform;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.api.sender.Sender;
import io.github.retrooper.packetevents.util.SpigotReflectionUtil;
@@ -9,7 +7,6 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
public class BukkitPlatformServer implements PlatformServer {
@Override
@@ -35,10 +32,6 @@ public class BukkitPlatformServer implements PlatformServer {
@Override
public double getTPS() {
// Folia throws UnsupportedOperationException on calling getTPS(), there is no API for getting TPS on Folia
if (GrimAPI.INSTANCE.getPlatform() == Platform.FOLIA) {
return Double.NaN;
}
return SpigotReflectionUtil.getTPS();
}
}
@@ -1,25 +1,20 @@
package ac.grim.grimac.platform.bukkit;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.GrimExternalAPI;
import ac.grim.grimac.api.GrimAPIProvider;
import ac.grim.grimac.api.GrimAbstractAPI;
import ac.grim.grimac.api.event.EventBus;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.command.CloudCommandService;
import ac.grim.grimac.internal.platform.bukkit.resolver.BukkitResolverRegistrar;
import ac.grim.grimac.events.GrimExtensionManager;
import ac.grim.grimac.manager.init.Initable;
import ac.grim.grimac.manager.init.load.PacketEventsInit;
import ac.grim.grimac.manager.init.start.ExemptOnlinePlayersOnReload;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.platform.api.Platform;
import ac.grim.grimac.platform.api.PlatformLoader;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.api.command.CommandService;
import ac.grim.grimac.platform.api.manager.ItemResetHandler;
import ac.grim.grimac.platform.api.manager.MessagePlaceHolderManager;
import ac.grim.grimac.platform.api.manager.CommandAdapter;
import ac.grim.grimac.platform.api.manager.PlatformPluginManager;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.player.PlatformPlayerFactory;
import ac.grim.grimac.platform.api.scheduler.PlatformScheduler;
import ac.grim.grimac.platform.api.sender.Sender;
@@ -33,11 +28,11 @@ import ac.grim.grimac.platform.bukkit.manager.BukkitParserDescriptorFactory;
import ac.grim.grimac.platform.bukkit.manager.BukkitPermissionRegistrationManager;
import ac.grim.grimac.platform.bukkit.manager.BukkitPlatformPluginManager;
import ac.grim.grimac.platform.bukkit.player.BukkitPlatformPlayerFactory;
import ac.grim.grimac.platform.bukkit.resolver.BukkitResolverRegistrar;
import ac.grim.grimac.platform.bukkit.scheduler.bukkit.BukkitPlatformScheduler;
import ac.grim.grimac.platform.bukkit.scheduler.folia.FoliaPlatformScheduler;
import ac.grim.grimac.platform.bukkit.sender.BukkitSenderFactory;
import ac.grim.grimac.platform.bukkit.utils.placeholder.PlaceholderAPIExpansion;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.lazy.LazyHolder;
import com.github.retrooper.packetevents.PacketEventsAPI;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
@@ -52,6 +47,7 @@ import org.incendo.cloud.brigadier.CloudBrigadierManager;
import org.incendo.cloud.bukkit.CloudBukkitCapabilities;
import org.incendo.cloud.execution.ExecutionCoordinator;
import org.incendo.cloud.paper.LegacyPaperCommandManager;
import org.incendo.cloud.setting.Configurable;
public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements PlatformLoader {
@@ -59,19 +55,13 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
public static GrimACBukkitLoaderPlugin LOADER;
private final LazyHolder<PlatformScheduler> scheduler = LazyHolder.simple(this::createScheduler);
private final LazyHolder<PacketEventsAPI<?>> packetEvents = LazyHolder.simple(() -> {
// External mode reuses the installed provider's API (plugin.yml depend orders load before this).
if (PacketEventsInit.isShadePE()) {
return SpigotPacketEventsBuilder.build(this);
}
return com.github.retrooper.packetevents.PacketEvents.getAPI();
});
private final LazyHolder<PacketEventsAPI<?>> packetEvents = LazyHolder.simple(() -> SpigotPacketEventsBuilder.build(this));
private final LazyHolder<BukkitSenderFactory> senderFactory = LazyHolder.simple(BukkitSenderFactory::new);
private final LazyHolder<CommandManager<Sender>> commandManager = LazyHolder.simple(this::createCommandManager);
private final LazyHolder<ItemResetHandler> itemResetHandler = LazyHolder.simple(BukkitItemResetHandler::new);
private final LazyHolder<CommandService> commandService = LazyHolder.simple(this::createCommandService);
private final CloudCommandAdapter commandAdapter = new BukkitParserDescriptorFactory();
@Getter private final PlatformPlayerFactory platformPlayerFactory = new BukkitPlatformPlayerFactory();
@Getter private final CommandAdapter commandAdapter = new BukkitParserDescriptorFactory();
@Getter private final PlatformPluginManager pluginManager = new BukkitPlatformPluginManager();
@Getter private final GrimPlugin plugin;
@Getter private final PlatformServer platformServer = new BukkitPlatformServer();
@@ -79,8 +69,9 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
@Getter private final BukkitPermissionRegistrationManager permissionManager = new BukkitPermissionRegistrationManager();
public GrimACBukkitLoaderPlugin() {
BukkitResolverRegistrar registrar = new BukkitResolverRegistrar();
registrar.registerAll(GrimAPI.INSTANCE.getExtensionManager());
GrimExtensionManager extensionManager = GrimAPI.INSTANCE.getExtensionManager();
BukkitResolverRegistrar registrar = new BukkitResolverRegistrar(extensionManager);
registrar.registerAll();
this.plugin = registrar.resolvePlugin(this);
}
@@ -125,13 +116,13 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
}
@Override
public ItemResetHandler getItemResetHandler() {
return itemResetHandler.get();
public CommandManager<Sender> getCommandManager() {
return commandManager.get();
}
@Override
public CommandService getCommandService() {
return commandService.get();
public ItemResetHandler getItemResetHandler() {
return itemResetHandler.get();
}
@Override
@@ -141,111 +132,25 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
@Override
public void registerAPIService() {
final GrimExternalAPI externalAPI = GrimAPI.INSTANCE.getExternalAPI();
final EventBus eventBus = externalAPI.getEventBus();
final ac.grim.grimac.api.plugin.GrimPlugin context = GrimAPI.INSTANCE.getGrimPlugin();
eventBus.subscribe(context, ac.grim.grimac.api.event.events.GrimJoinEvent.class, (event) -> {
ac.grim.grimac.api.events.GrimJoinEvent bukkitEvent =
new ac.grim.grimac.api.events.GrimJoinEvent(event.getUser());
Bukkit.getPluginManager().callEvent(bukkitEvent);
});
eventBus.subscribe(context, ac.grim.grimac.api.event.events.GrimQuitEvent.class, (event) -> {
ac.grim.grimac.api.events.GrimQuitEvent bukkitEvent =
new ac.grim.grimac.api.events.GrimQuitEvent(event.getUser());
Bukkit.getPluginManager().callEvent(bukkitEvent);
});
eventBus.subscribe(context, ac.grim.grimac.api.event.events.GrimReloadEvent.class, (event) -> {
ac.grim.grimac.api.events.GrimReloadEvent bukkitEvent =
new ac.grim.grimac.api.events.GrimReloadEvent(event.isSuccess());
Bukkit.getPluginManager().callEvent(bukkitEvent);
});
eventBus.subscribe(context, ac.grim.grimac.api.event.events.FlagEvent.class, (event) -> {
ac.grim.grimac.api.events.FlagEvent bukkitEvent =
new ac.grim.grimac.api.events.FlagEvent(
event.getUser(),
event.getCheck(),
event.getVerbose()
);
Bukkit.getPluginManager().callEvent(bukkitEvent);
if (bukkitEvent.isCancelled()) {
event.setCancelled(true);
}
});
eventBus.subscribe(context, ac.grim.grimac.api.event.events.CommandExecuteEvent.class, (event) -> {
ac.grim.grimac.api.events.CommandExecuteEvent bukkitEvent =
new ac.grim.grimac.api.events.CommandExecuteEvent(
event.getUser(),
event.getCheck(),
event.getVerbose(),
event.getCommand()
);
Bukkit.getPluginManager().callEvent(bukkitEvent);
if (bukkitEvent.isCancelled()) {
event.setCancelled(true);
}
});
eventBus.subscribe(context, ac.grim.grimac.api.event.events.CompletePredictionEvent.class, (event) -> {
// Note: New event doesn't have verbose, passing null or check name is standard fallback
ac.grim.grimac.api.events.CompletePredictionEvent bukkitEvent =
new ac.grim.grimac.api.events.CompletePredictionEvent(
event.getUser(),
event.getCheck(),
"",
event.getOffset()
);
Bukkit.getPluginManager().callEvent(bukkitEvent);
if (bukkitEvent.isCancelled()) {
event.setCancelled(true);
}
});
GrimAPIProvider.init(externalAPI);
Bukkit.getServicesManager().register(GrimAbstractAPI.class, externalAPI, GrimACBukkitLoaderPlugin.LOADER, ServicePriority.Normal);
GrimAPIProvider.init(GrimAPI.INSTANCE.getExternalAPI());
Bukkit.getServicesManager().register(GrimAbstractAPI.class, GrimAPI.INSTANCE.getExternalAPI(), GrimACBukkitLoaderPlugin.LOADER, ServicePriority.Normal);
}
private PlatformScheduler createScheduler() {
return GrimAPI.INSTANCE.getPlatform() == Platform.FOLIA ? new FoliaPlatformScheduler() : new BukkitPlatformScheduler();
}
private CommandService createCommandService() {
try {
return new CloudCommandService(this::createCloudCommandManager, commandAdapter);
} catch (Throwable t) {
LogUtil.warn("CRITICAL: Failed to initialize Command Framework. " +
"Grim will continue to run with no commands.", t);
return () -> {};
}
}
private CommandManager<Sender> createCloudCommandManager() {
private CommandManager<Sender> createCommandManager() {
LegacyPaperCommandManager<Sender> manager = new LegacyPaperCommandManager<>(
this,
ExecutionCoordinator.simpleCoordinator(),
senderFactory.get()
);
if (manager.hasCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
try {
manager.registerBrigadier();
CloudBrigadierManager<Sender, ?> cbm = manager.brigadierManager();
cbm.settings().set(BrigadierSetting.FORCE_EXECUTABLE, true);
} catch (Throwable t) {
LogUtil.error("Failed to register Brigadier native completions. Falling back to standard completions.", t);
}
manager.registerBrigadier();
CloudBrigadierManager<Sender, ?> cbm = manager.brigadierManager();
Configurable<BrigadierSetting> settings = cbm.settings();
settings.set(BrigadierSetting.FORCE_EXECUTABLE, true);
} else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
manager.registerAsynchronousCompletions();
}
@@ -63,6 +63,7 @@ public class BukkitGrimEntity implements GrimEntity {
if (bukkitPlatformWorld == null || !bukkitPlatformWorld.getBukkitWorld().equals(entity.getWorld())) {
bukkitPlatformWorld = new BukkitPlatformWorld(entity.getWorld());
}
return bukkitPlatformWorld;
}
@@ -4,8 +4,10 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.PistonTemplate;
import ac.grim.grimac.utils.data.PistonData;
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;
@@ -26,10 +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(int x, int y, int z, double posX, double posY, double posZ) {
return Math.abs(x - posX) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(y - posY) <= MAX_VERTICAL_DISTANCE
&& Math.abs(z - posZ) <= MAX_HORIZONTAL_DISTANCE;
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)
@@ -66,8 +68,18 @@ public class PistonEvent implements Listener {
piston.getY() + event.getDirection().getModY(),
piston.getZ() + event.getDirection().getModZ()));
PistonTemplate data = new PistonTemplate(BukkitConversionUtils.fromBukkitFace(event.getDirection()), boxes, true, hasSlimeBlock, hasHoneyBlock);
addPistonData(data, piston.getX(), piston.getY(), piston.getZ());
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
final BlockFace blockFace = BukkitConversionUtils.fromBukkitFace(event.getDirection());
final Vector3i sourcePos = new Vector3i(piston.getX(), piston.getY(), piston.getZ());
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (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));
}
}
}
// For some unknown reason, bukkit handles this stupidly
@@ -89,8 +101,9 @@ public class PistonEvent implements Listener {
BlockFace face = BukkitConversionUtils.fromBukkitFace(event.getDirection());
// The event was called without blocks and is therefore in the right direction
Block piston = event.getBlock();
if (event.getBlocks().isEmpty()) {
Block piston = event.getBlock();
// Add bounding box of the actual piston head pushing
boxes.add(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true)
.offset(piston.getX() + face.getModX(),
@@ -114,19 +127,16 @@ public class PistonEvent implements Listener {
}
}
PistonTemplate data = new PistonTemplate(face, boxes, false, hasSlimeBlock, hasHoneyBlock);
addPistonData(data, piston.getX(), piston.getY(), piston.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());
private void addPistonData(PistonTemplate pistonTemplate, int blockX, int blockY, int blockZ) {
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(blockX >> 4, blockZ >> 4) || !isCloseEnough(blockX, blockY, blockZ, player.x, player.y, player.z)) continue;
int lastTrans = player.lastTransactionSent.get();
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> {
player.compensatedWorld.addPiston(pistonTemplate, lastTrans);
});
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));
}
}
}
}
@@ -8,7 +8,6 @@ import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.lists.HookedListWrapper;
import ac.grim.grimac.utils.reflection.ReflectionUtils;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.reflection.Reflection;
@@ -26,20 +25,6 @@ import java.util.List;
@SuppressWarnings(value = {"unchecked", "deprecated"})
public class BukkitTickEndEvent extends AbstractTickEndEvent implements Listener {
private Boolean getLateBindState() {
Class<?> spigotConfig = ReflectionUtils.getClass("org.spigotmc.SpigotConfig");
// ReflectionUtils.getField(class, name) handles the loop and setAccessible
Field field = ReflectionUtils.getField(spigotConfig, "lateBind");
if (field == null) return null;
try {
return (boolean) field.get(null);
} catch (Exception ignored) {
return null;
}
}
@Override
public void start() {
if (!super.shouldInjectEndTick()) {
@@ -53,18 +38,9 @@ public class BukkitTickEndEvent extends AbstractTickEndEvent implements Listener
PaperUtils.registerTickEndEvent(this, this::tickAllFoliaPlayers);
return;
}
// if it fails to register Paper event, try to inject via reflection
if (!PaperUtils.registerTickEndEvent(this, this::tickAllPlayers) && !injectWithReflection()) {
// if it fails to inject, try to use paper's event
if (!injectWithReflection() && !PaperUtils.registerTickEndEvent(this, this::tickAllPlayers)) {
LogUtil.error("Failed to inject into the end of tick event!");
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_14_4)) {
Boolean lateBind = getLateBindState();
if (lateBind == null) {
LogUtil.error("Failed to determine the late-bind state. Perhaps you are using a custom server fork? Check the fork configuration for a late-bind option and disable it.");
} else if (lateBind) {
LogUtil.error("Injection failed because the late-bind option is enabled. Disable it in spigot.yml.");
}
}
}
}
@@ -89,7 +65,6 @@ public class BukkitTickEndEvent extends AbstractTickEndEvent implements Listener
// Inject so we can add the final transaction pre-flush event
try {
Object connection = SpigotReflectionUtil.getMinecraftServerConnectionInstance();
if (connection == null) return false;
Field connectionsList = Reflection.getField(connection.getClass(), List.class, 1);
List<Object> endOfTickObject = (List<Object>) connectionsList.get(connection);
@@ -1,7 +1,7 @@
package ac.grim.grimac.platform.bukkit.manager;
import ac.grim.grimac.platform.api.command.PlayerSelector;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.manager.CommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.bukkit.command.BukkitPlayerSelectorParser;
import org.bukkit.Bukkit;
@@ -16,7 +16,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class BukkitParserDescriptorFactory implements CloudCommandAdapter {
public class BukkitParserDescriptorFactory implements CommandAdapter {
// Parser is kept as a field because its stateless
private final BukkitPlayerSelectorParser<Sender> bukkitPlayerSelectorParser = new BukkitPlayerSelectorParser<>();
@@ -1,7 +1,6 @@
package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.entity.GrimEntity;
import ac.grim.grimac.platform.api.player.BlockTranslator;
import ac.grim.grimac.platform.api.player.PlatformInventory;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
@@ -18,6 +17,7 @@ import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.Vector3d;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.Getter;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import org.bukkit.World;
import org.bukkit.entity.Player;
@@ -31,6 +31,8 @@ import java.util.concurrent.CompletableFuture;
public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPlayer {
private static final BukkitAudiences audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
@Getter
private final Player bukkitPlayer;
@Getter
@@ -89,8 +91,7 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
// Paper/Purpur Player is natively an Adventure Audience, so no platform shim is needed
bukkitPlayer.sendMessage(message);
audiences.player(bukkitPlayer).sendMessage(message);
}
}
@@ -169,11 +170,6 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
return GrimACBukkitLoaderPlugin.LOADER.getBukkitSenderFactory().map(this.bukkitPlayer);
}
@Override
public BlockTranslator getBlockTranslator() {
return BlockTranslator.IDENTITY;
}
@Override
@NotNull
public Player getNative() {
@@ -8,7 +8,6 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
@@ -55,14 +54,4 @@ public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory<P
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(name);
return new BukkitOfflinePlatformPlayer(offlinePlayer);
}
@Override
public Collection<OfflinePlatformPlayer> getOfflinePlayers() {
OfflinePlayer[] offlinePlayers = Bukkit.getOfflinePlayers();
BukkitOfflinePlatformPlayer[] offlinePlatformPlayers = new BukkitOfflinePlatformPlayer[offlinePlayers.length];
for (int i = 0; i < offlinePlayers.length; i++) {
offlinePlatformPlayers[i] = new BukkitOfflinePlatformPlayer(offlinePlayers[i]);
}
return Arrays.asList(offlinePlatformPlayers);
}
}
@@ -0,0 +1,111 @@
package ac.grim.grimac.platform.bukkit.resolver;
import ac.grim.grimac.api.plugin.BasicGrimPlugin;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.events.GrimExtensionManager;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the registration of Bukkit-specific resolvers with the GrimExtensionManager.
* This class is designed to be instantiated once during plugin startup.
*/
public final class BukkitResolverRegistrar {
private final GrimExtensionManager extensionManager;
// Cache to ensure we only create one GrimPlugin wrapper per Bukkit Plugin instance.
private final Map<Plugin, GrimPlugin> pluginCache = new ConcurrentHashMap<>();
public BukkitResolverRegistrar(GrimExtensionManager extensionManager) {
this.extensionManager = extensionManager;
}
/**
* Registers all the Bukkit-specific resolvers in order of performance (fastest to slowest).
*/
public void registerAll() {
extensionManager.setFailureHandler(this::createFailureException);
extensionManager.registerResolver(this::resolvePluginInstance);
extensionManager.registerResolver(this::resolveStringName);
extensionManager.registerResolver(this::resolveClass);
}
/**
* The core logic for converting a Bukkit Plugin into a GrimPlugin, with caching.
*/
public GrimPlugin resolvePlugin(Plugin bukkitPlugin) {
return pluginCache.computeIfAbsent(bukkitPlugin, plugin -> {
PluginDescriptionFile desc = plugin.getDescription();
return new BasicGrimPlugin(
plugin.getLogger(),
plugin.getDataFolder(),
desc.getVersion(),
desc.getDescription(),
desc.getAuthors()
);
});
}
/**
* 1. Resolver for raw Plugin instances (the most common case).
*/
private GrimPlugin resolvePluginInstance(Object context) {
if (context instanceof Plugin bukkitPlugin) {
return resolvePlugin(bukkitPlugin);
}
return null;
}
/**
* 2. Resolver for String plugin names.
*/
private GrimPlugin resolveStringName(Object context) {
if (context instanceof String pluginName) {
// Use the Bukkit API to look up the plugin by name. This returns null if not found.
Plugin bukkitPlugin = Bukkit.getPluginManager().getPlugin(pluginName);
// We must check for null before applying the logic to avoid a NullPointerException.
return (bukkitPlugin != null) ? resolvePlugin(bukkitPlugin) : null;
}
return null;
}
/**
* 3. Resolver for Class objects.
*/
private GrimPlugin resolveClass(Object context) {
if (context instanceof Class) {
try {
// First, resolve the Class to a Plugin instance.
JavaPlugin providingPlugin = JavaPlugin.getProvidingPlugin((Class<?>) context);
// Then, use our shared logic directly to get the GrimPlugin wrapper.
return resolvePlugin(providingPlugin);
} catch (IllegalArgumentException | IllegalStateException e) {
// This is an expected failure if the class is not from a plugin
// or is called from a static initializer. We simply indicate that
// this resolver cannot handle this context.
return null;
}
}
// This resolver only handles Classes.
return null;
}
private RuntimeException createFailureException(Object failedContext) {
String message = """
Failed to resolve GrimPlugin context from the provided object of type '%s'.
Please ensure you are passing one of the following:
- The main instance of your plugin (e.g., 'this' from your class extending JavaPlugin).
- The plugin name as a String (e.g., "MyPluginName").
- Any Class from your plugin's JAR file (e.g., MyListener.class).
- A pre-existing GrimPlugin instance.
""".formatted(failedContext.getClass().getName());
return new IllegalArgumentException(message);
}
}
@@ -3,6 +3,8 @@ package ac.grim.grimac.platform.bukkit.sender;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.api.sender.SenderFactory;
import ac.grim.grimac.platform.bukkit.GrimACBukkitLoaderPlugin;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
@@ -16,6 +18,11 @@ import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class BukkitSenderFactory extends SenderFactory<CommandSender> implements SenderMapper<CommandSender, Sender> {
private final BukkitAudiences audiences;
public BukkitSenderFactory() {
this.audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
}
@Override
protected String getName(CommandSender sender) {
@@ -35,13 +42,12 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> implements
@Override
protected void sendMessage(CommandSender sender, Component message) {
// we can safely send async for players and the console - otherwise, send it sync
// Paper/Purpur CommandSender is natively an Adventure Audience, so no platform shim is needed
if (sender instanceof Player || sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender) {
sender.sendMessage(message);
this.audiences.sender(sender).sendMessage(message);
} else {
GrimAPI.INSTANCE.getScheduler().getGlobalRegionScheduler().run(
GrimAPI.INSTANCE.getGrimPlugin(),
() -> sender.sendMessage(message)
() -> this.audiences.sender(sender).sendMessage(message)
);
}
}
+50 -31
View File
@@ -1,52 +1,75 @@
import versioning.BuildConfig
plugins {
eclipse
`maven-publish`
grim.`base-conventions`
}
repositories {
// We still call mavenLocal() conditionally at the top for non-exclusive deps (general fallback)
if (BuildConfig.mavenLocalOverride) mavenLocal()
// Grim API
exclusive("https://repo.grim.ac/snapshots") {
includeGroup("ac.grim.grimac")
if (BuildConfig.mavenLocalOverride) {
mavenLocal()
}
// PacketEvents
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
includeGroup("com.github.retrooper")
exclusiveContent {
forRepository {
maven("https://repo.grim.ac/snapshots") // Grim API
}
filter {
includeGroup("ac.grim.grimac")
includeGroup("com.github.retrooper")
}
}
// ViaVersion
exclusive("https://repo.viaversion.com", { mavenContent { releasesOnly() } }) {
includeGroup("com.viaversion")
exclusiveContent {
forRepository {
maven("https://repo.viaversion.com") { // ViaVersion
mavenContent { releasesOnly() }
}
}
filter {
includeGroup("com.viaversion")
}
}
// Configuralize
exclusive("https://nexus.scarsz.me/content/repositories/releases", { mavenContent { releasesOnly() } }) {
includeGroup("github.scarsz")
exclusiveContent {
forRepository {
maven("https://nexus.scarsz.me/content/repositories/releases") { // Configuralize
mavenContent { releasesOnly() }
}
}
filter {
includeGroup("github.scarsz")
}
}
// Cumulus
exclusive("https://repo.opencollab.dev/maven-releases/", { mavenContent { releasesOnly() } }) {
includeGroup("org.geysermc.api")
exclusiveContent {
forRepository {
maven("https://repo.opencollab.dev/maven-releases/") { // Cumulus (for Floodgate)
mavenContent { releasesOnly() }
}
}
filter {
includeGroup("org.geysermc.api")
}
}
// Floodgate
exclusive("https://repo.opencollab.dev/maven-snapshots/", { mavenContent { snapshotsOnly() } }) {
includeGroup("org.geysermc.floodgate")
includeGroup("org.geysermc.cumulus")
includeModule("org.geysermc", "common")
includeModule("org.geysermc", "geyser-parent")
exclusiveContent {
forRepository {
maven("https://repo.opencollab.dev/maven-snapshots/") { // Floodgate
mavenContent { snapshotsOnly() }
}
}
filter {
includeGroup("org.geysermc.floodgate")
includeGroup("org.geysermc.cumulus")
includeModule("org.geysermc", "common")
includeModule("org.geysermc", "geyser-parent")
}
}
mavenCentral()
}
dependencies {
if (BuildConfig.shadePE) {
api(libs.packetevents.api)
@@ -64,15 +87,11 @@ dependencies {
// Bump snakeyaml (transitive dep of configuralize) 1.29 -> 2.2+ for geyser-fabric
api(libs.snakeyaml)
api(libs.fastutil)
// Provided by the server at runtime; bundling it would shadow the platform's own Adventure
compileOnly(libs.adventure.text.minimessage)
api(libs.adventure.text.minimessage)
api(libs.jetbrains.annotations)
api(libs.hikaricp)
api(libs.postgresql) // JDBC driver for /grim history postgresql backend
api(libs.grim.api)
api(libs.grim.internal)
compileOnly(libs.grim.internal.shims)
compileOnly(libs.geyser.base.api) {
isTransitive = false // messes with guava otherwise
@@ -2,8 +2,8 @@ package ac.grim.grimac;
import ac.grim.grimac.api.event.EventBus;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.internal.plugin.resolver.GrimExtensionManager;
import ac.grim.grimac.internal.event.OptimizedEventBus;
import ac.grim.grimac.events.GrimExtensionManager;
import ac.grim.grimac.events.OptimizedEventBus;
import ac.grim.grimac.manager.AlertManagerImpl;
import ac.grim.grimac.manager.DiscordManager;
import ac.grim.grimac.manager.InitManager;
@@ -15,20 +15,23 @@ import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
import ac.grim.grimac.platform.api.Platform;
import ac.grim.grimac.platform.api.PlatformLoader;
import ac.grim.grimac.platform.api.PlatformServer;
import ac.grim.grimac.platform.api.command.CommandService;
import ac.grim.grimac.platform.api.manager.ItemResetHandler;
import ac.grim.grimac.platform.api.manager.MessagePlaceHolderManager;
import ac.grim.grimac.platform.api.manager.CommandAdapter;
import ac.grim.grimac.platform.api.manager.PermissionRegistrationManager;
import ac.grim.grimac.platform.api.manager.PlatformPluginManager;
import ac.grim.grimac.platform.api.player.PlatformPlayerFactory;
import ac.grim.grimac.platform.api.scheduler.PlatformScheduler;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.api.sender.SenderFactory;
import ac.grim.grimac.utils.anticheat.PlayerDataManager;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import ac.grim.grimac.utils.reflection.ReflectionUtils;
import lombok.Getter;
import org.incendo.cloud.CommandManager;
import org.jetbrains.annotations.NotNull;
@Getter
public final class GrimAPI {
public static final GrimAPI INSTANCE = new GrimAPI();
@@ -75,7 +78,7 @@ public final class GrimAPI {
public void load(PlatformLoader platformLoader, Initable... platformSpecificInitables) {
this.loader = platformLoader;
this.violationDatabaseManager = new ViolationDatabaseManager(getGrimPlugin());
this.initManager = new InitManager(loader.getPacketEvents(), platformSpecificInitables);
this.initManager = new InitManager(loader.getPacketEvents(), loader::getCommandManager, platformSpecificInitables);
this.initManager.load();
this.initialized = true;
}
@@ -98,6 +101,10 @@ public final class GrimAPI {
return loader.getPlatformPlayerFactory();
}
public CommandAdapter getCommandAdapter() {
return loader.getCommandAdapter();
}
public GrimPlugin getGrimPlugin() {
return loader.getPlugin();
}
@@ -122,8 +129,8 @@ public final class GrimAPI {
return loader.getMessagePlaceHolderManager();
}
public CommandService getCommandService() {
return loader.getCommandService();
public CommandManager<Sender> getCommandManager() {
return loader.getCommandManager();
}
private void checkInitialized() {
@@ -6,7 +6,6 @@ import ac.grim.grimac.api.alerts.AlertManager;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.api.event.EventBus;
import ac.grim.grimac.api.event.events.GrimReloadEvent;
import ac.grim.grimac.api.plugin.GrimPlugin;
import ac.grim.grimac.manager.config.ConfigManagerFileImpl;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.player.GrimPlayer;
@@ -14,7 +13,6 @@ import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.common.ConfigReloadObserver;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -47,11 +45,6 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
return api.getEventBus();
}
@Override
public @Nullable GrimUser getGrimUser(Player player) {
return getGrimUser(player.getUniqueId());
}
@Override
public @Nullable GrimUser getGrimUser(UUID uuid) {
return api.getPlayerDataManager().getPlayer(uuid);
@@ -114,11 +107,6 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
return GrimAPI.INSTANCE.getTickManager().currentTick;
}
@Override
public @NotNull GrimPlugin getGrimPlugin(@NotNull Object o) {
return this.api.getExtensionManager().getPlugin(o);
}
// on load, load the config & register the service
public void load() {
reload(configManagerFile);
@@ -8,7 +8,6 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
@@ -61,11 +60,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
}
public boolean shouldModifyPackets() {
return isEnabled
&& !player.disableGrim
&& !player.noModifyPacketPermission
&& !noModifyPacketPermission
&& !exemptPermission;
return isEnabled && !player.disableGrim && !player.noModifyPacketPermission && !exemptPermission;
}
public final void updatePermissions() {
@@ -101,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;
}
@@ -176,8 +171,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
public static boolean isAsync(PacketTypeCommon packetType) {
return packetType == PacketType.Play.Client.KEEP_ALIVE
|| packetType == PacketType.Play.Client.CHUNK_BATCH_ACK
|| packetType == PacketType.Play.Client.RESOURCE_PACK_STATUS;
|| packetType == PacketType.Play.Client.CHUNK_BATCH_ACK;
}
public boolean isUpdate(PacketTypeCommon packetType) {
@@ -209,10 +203,4 @@ public class Check extends GrimProcessor implements AbstractCheck {
return isFlying(packetType);
}
// prevent causing exploits with packet cancelling (ie noslow)
public boolean canCancel(DiggingAction action) {
return action != DiggingAction.RELEASE_USE_ITEM
// we check client version here because 1.8- doesn't predict dropping items, so we can cancel them. (see CompensatedInventory)
&& (action != DiggingAction.DROP_ITEM && action != DiggingAction.DROP_ITEM_STACK || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8));
}
}
@@ -8,7 +8,7 @@ public abstract class AbstractDebugHandler extends Check {
super(player);
}
public abstract boolean toggleListener(GrimPlayer player);
public abstract void toggleListener(GrimPlayer player);
public abstract boolean toggleConsoleOutput();
}
@@ -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;
}
}
@@ -1,209 +0,0 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerPluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Handles debug visualization of hitboxes and reach calculations for GrimAC.
* Sends debug data to clients through plugin messages that can be visualized by compatible clients.
*/
public class HitboxDebugHandler extends AbstractDebugHandler {
/**
* Set of players currently listening to hitbox debug data
*/
private final Set<GrimPlayer> listeners = new CopyOnWriteArraySet<>(new HashSet<>());
/**
* Creates a new HitboxDebugHandler for the specified player
*
* @param grimPlayer The GrimAC player instance to debug
*/
public HitboxDebugHandler(GrimPlayer grimPlayer) {
super(grimPlayer);
}
/**
* Toggles whether a player receives hitbox debug visualization data.
* If the player is already listening, they will be removed. If not, they will be added.
*
* @param player The player to toggle debug visualization for
* @return {@code true} if the player is now listening (was added),
* {@code false} if the player is no longer listening (was removed).
*/
@Override
public boolean toggleListener(GrimPlayer player) {
boolean wasPresent = listeners.remove(player);
if (wasPresent) {
return false;
} else {
listeners.add(player);
return true;
}
}
@Override
public boolean toggleConsoleOutput() {
throw new UnsupportedOperationException();
}
/**
* Sends debug visualization data for reach calculations to all listening players.
* The data includes hitboxes, target entities, look vectors, and eye heights used in reach calculations.
*
* @param hitboxes Map of entity IDs to their collision boxes
* @param targetEntities Set of entity IDs that are considered targets
* @param lookVecsAndEyeHeights List of pairs containing look vectors and their corresponding eye heights
* @param baseX Base x coordinate before eye height adjustments
* @param baseY Base y coordinate before eye height adjustments
* @param baseZ Base z coordinate before eye height adjustments
* @param isPrediction Whether these hitboxes are from a prediction calculation
*
* Packet Format (Version 1):
* - Byte: Version (0)
* - Byte: Global flags
* - Bit 0: isPrediction
* - Bits 1-7: Reserved
* - Double: Player reach/interact distance
* - Vector3d: Base position (3 doubles)
* - VarInt: Number of ray traces
* - For each ray trace:
* - Double: Eye height delta
* - Vector3d: Look vector (3 doubles)
* - VarInt: Number of hitboxes
* - For each hitbox:
* - VarInt: Entity ID
* - Byte: Flags
* - Bit 0: Is target entity
* - Bit 1: Is no collision
* - Bits 2-7: Reserved
* - If not NoCollisionBox:
* - Double: minX
* - Double: minY
* - Double: minZ
* - Double: maxX
* - Double: maxY
* - Double: maxZ
*/
public void sendHitboxData(Map<Integer, CollisionBox> hitboxes, Set<Integer> targetEntities,
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, double baseX, double baseY, double baseZ,
boolean isPrediction, double reachDistance) {
if (!isEnabled()) return;
ByteBuf buffer = Unpooled.buffer();
try {
// Version Header
buffer.writeByte(1);
// Global Flags Header
// Pack boolean flags into a single byte
byte global_flags = 0;
global_flags |= (isPrediction ? 1 : 0); // Bit 0: are the hitboxes from a prediction?
// Bits 2-7 reserved for future use
buffer.writeByte(global_flags);
// Write reach distance
buffer.writeDouble(reachDistance);
// Write base position
writeVector(buffer, baseX, baseY, baseZ);
// Write number of ray traces
ByteBufHelper.writeVarInt(buffer, lookVecsAndEyeHeights.size());
// Write all possible ray traces
for (Pair<Vector3dm, Double> pair : lookVecsAndEyeHeights) {
Vector3dm lookVec = pair.first();
double eyeHeight = pair.second();
// Write eye height delta and look vector
// we make them 3 meters shorter because all of the vectors passed into here are made 3 meters longer
// then they need to be so grim can report correct reach distance for too far away hits
buffer.writeDouble(eyeHeight);
writeVector(buffer, lookVec.getX(), lookVec.getY(), lookVec.getZ());
}
// Write number of hitboxes
ByteBufHelper.writeVarInt(buffer, hitboxes.size());
// Write hitbox data
for (Map.Entry<Integer, CollisionBox> entry : hitboxes.entrySet()) {
int entityId = entry.getKey();
CollisionBox box = entry.getValue();
// Write entity ID
ByteBufHelper.writeVarInt(buffer, entityId);
// Pack boolean flags into a single byte
byte flags = 0;
flags |= (targetEntities.contains(entityId) ? 1 : 0); // Bit 0: Is target
flags |= (box == NoCollisionBox.INSTANCE ? 2 : 0); // Bit 1: Is no collision
// Bits 2-7 reserved for future use
buffer.writeByte(flags);
// Write box coordinates if it's not a NoCollisionBox
if ((flags & 2) == 0) {
SimpleCollisionBox simpleCollisionBox = (SimpleCollisionBox) box;
buffer.writeDouble(simpleCollisionBox.minX);
buffer.writeDouble(simpleCollisionBox.minY);
buffer.writeDouble(simpleCollisionBox.minZ);
buffer.writeDouble(simpleCollisionBox.maxX);
buffer.writeDouble(simpleCollisionBox.maxY);
buffer.writeDouble(simpleCollisionBox.maxZ);
}
}
// Convert buffer to byte array
byte[] data = new byte[buffer.readableBytes()];
buffer.readBytes(data);
// Create and send packet
WrapperPlayServerPluginMessage packet = new WrapperPlayServerPluginMessage(
"grim:debug_hitbox",
data
);
// Send to all listeners
for (GrimPlayer listener : listeners) {
if (listener != null) {
listener.user.sendPacket(packet);
}
}
} finally {
// Release buffer to prevent memory leaks
buffer.release();
}
}
public boolean isEnabled() {
return !listeners.isEmpty();
}
/**
* Helper method to write vector coordinates to the ByteBuf
* @param buffer The buffer to write to
* @param x The x coordinate to write
* @param y The y coordinate to write
* @param z The z coordinate to write
*/
private void writeVector(ByteBuf buffer, double x, double y, double z) {
buffer.writeDouble(x);
buffer.writeDouble(y);
buffer.writeDouble(z);
}
}
@@ -6,21 +6,26 @@ import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
import org.jetbrains.annotations.NotNull;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsC", description = "Tried to wake up while not sleeping", experimental = true)
@CheckData(name = "BadPacketsC", description = "Interacted with self")
public class BadPacketsC extends Check implements PacketCheck {
public BadPacketsC(@NotNull GrimPlayer player) {
public BadPacketsC(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.LEAVE_BED
&& !player.isInBed) {
flagAndAlert();
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (player.gamemode == GameMode.SPECTATOR) return;
if (new WrapperPlayClientInteractEntity(event).getEntityId() == player.entityID) {
// Instant ban
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -10,6 +10,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUseItem;
import java.util.ArrayList;
@@ -25,7 +26,7 @@ public class BadPacketsJ extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.cameraEntity.isSelf()) {
if (player.gamemode == GameMode.SPECTATOR) {
rotations.clear();
return;
}
@@ -43,7 +43,7 @@ public class BadPacketsL extends Check implements PacketCheck {
+ ", face=" + packet.getBlockFace()
+ ", sequence=" + packet.getSequence()
+ ", action=" + packet.getAction().toString().toLowerCase(Locale.ROOT)
) && shouldModifyPackets() && canCancel(packet.getAction())) {
) && shouldModifyPackets() && packet.getAction() != DiggingAction.RELEASE_USE_ITEM) {
event.setCancelled(true);
player.onPacketCancel();
}
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCl
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClickWindow.WindowClickType;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerOpenWindow;
@CheckData(name = "BadPacketsP", description = "Invalid window click packet", experimental = true)
@CheckData(name = "BadPacketsP", description = "Invalid click packets", experimental = true)
public class BadPacketsP extends Check implements PacketCheck {
private int containerType = -1;
@@ -6,6 +6,7 @@ import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
@CheckData(name = "BadPacketsR", decay = 0.25, experimental = true)
public class BadPacketsR extends Check implements PacketCheck {
@@ -22,9 +23,9 @@ 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.cameraEntity.isSelf() && !player.compensatedEntities.self.isDead) {
if (positions == 0 && clock != 0 && player.gamemode != GameMode.SPECTATOR && !player.compensatedEntities.self.isDead) {
flag("time=" + ms + "ms, " + "lst=" + diff + "ms, positions=" + positions);
} else {
reward();
@@ -33,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();
}
}
@@ -10,7 +10,6 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsT")
@@ -36,39 +35,37 @@ public class BadPacketsT extends Check implements PacketCheck {
if (event.getPacketType().equals(PacketType.Play.Client.INTERACT_ENTITY)) {
final WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
// Only INTERACT_AT actually has an interaction vector
if (wrapper.getAction() != WrapperPlayClientInteractEntity.InteractAction.INTERACT_AT) return;
Vector3f targetVector = wrapper.getTarget().orElse(null);
if (targetVector == null) return; // shouldn't ever happen, but whatever
wrapper.getTarget().ifPresent(targetVector -> {
final PacketEntity packetEntity = player.compensatedEntities.getEntity(wrapper.getEntityId());
// Don't continue if the compensated entity hasn't been resolved
if (packetEntity == null) {
return;
}
final PacketEntity packetEntity = player.compensatedEntities.getEntity(wrapper.getEntityId());
// Don't continue if the compensated entity hasn't been resolved
if (packetEntity == null) {
return;
}
// Make sure our target entity is actually a player (Player NPCs work too)
if (!EntityTypes.PLAYER.equals(packetEntity.type)) {
// We can't check for any entity that is not a player
return;
}
// Make sure our target entity is actually a player (Player NPCs work too)
if (!EntityTypes.PLAYER.equals(packetEntity.type)) {
// We can't check for any entity that is not a player
return;
}
// Perform the interaction vector check
// TODO:
// 27/12/2023 - Dynamic values for more than just one entity type?
// 28/12/2023 - Player-only is fine
// 30/12/2023 - Expansions differ in 1.9+
final float scale = (float) packetEntity.getAttributeValue(Attributes.SCALE);
if (targetVector.y > (minVerticalDisplacement * scale) && targetVector.y < (maxVerticalDisplacement * scale)
&& Math.abs(targetVector.x) < (maxHorizontalDisplacement * scale)
&& Math.abs(targetVector.z) < (maxHorizontalDisplacement * scale)) {
return;
}
// Perform the interaction vector check
// TODO:
// 27/12/2023 - Dynamic values for more than just one entity type?
// 28/12/2023 - Player-only is fine
// 30/12/2023 - Expansions differ in 1.9+
final float scale = (float) packetEntity.getAttributeValue(Attributes.SCALE);
if (targetVector.y > (minVerticalDisplacement * scale) && targetVector.y < (maxVerticalDisplacement * scale)
&& Math.abs(targetVector.x) < (maxHorizontalDisplacement * scale)
&& Math.abs(targetVector.z) < (maxHorizontalDisplacement * scale)) {
return;
}
// Log the vector
final String verbose = String.format("%.5f/%.5f/%.5f",
targetVector.x, targetVector.y, targetVector.z);
// We could pretty much ban the player at this point
flagAndAlert(verbose);
// Log the vector
final String verbose = String.format("%.5f/%.5f/%.5f",
targetVector.x, targetVector.y, targetVector.z);
// We could pretty much ban the player at this point
flagAndAlert(verbose);
});
}
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "BadPacketsX", experimental = true)
@@ -41,7 +42,7 @@ public class BadPacketsX extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
sprint = sneak = false;
return;
}
@@ -10,6 +10,7 @@ import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
@CheckData(name = "FarBreak", description = "Breaking blocks too far away", experimental = true)
public class FarBreak extends Check implements BlockBreakCheck {
@@ -19,7 +20,7 @@ public class FarBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (!player.cameraEntity.isSelf() || player.inVehicle() || blockBreak.action == DiggingAction.CANCELLED_DIGGING)
if (player.gamemode == GameMode.SPECTATOR || player.inVehicle() || blockBreak.action == DiggingAction.CANCELLED_DIGGING)
return; // falses
double min = Double.MAX_VALUE;
@@ -67,12 +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() - (targetBlockPosition == null ? 50 : 0); // ???
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;
@@ -80,8 +80,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
blockDelayBalance += 300 - breakDelay;
}
// Skip flagging clients whose tick rate is unobservable, like MultiBreak does
if (blockDelayBalance > 1000 && player.isTickingReliablyFor(3)) { // If more than a second of advantage
if (blockDelayBalance > 1000) { // If more than a second of advantage
if (flagAndAlert("delay=" + breakDelay + "ms, type=" + blockBreak.block.getType()) && shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -92,7 +91,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
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();
@@ -103,15 +102,14 @@ public class FastBreak extends Check implements BlockBreakCheck {
blockBreakBalance += diff;
}
// Skip flagging clients whose tick rate is unobservable, like MultiBreak does
if (blockBreakBalance > 1000 && player.isTickingReliablyFor(3)) { // If more than a second of advantage
if (blockBreakBalance > 1000) { // If more than a second of advantage
if (flagAndAlert("diff=" + diff + "ms, balance=" + blockBreakBalance + "ms, type=" + blockBreak.block.getType()) && shouldModifyPackets()) {
blockBreak.cancel();
}
}
// also set start time because the breaking netcode is fucked on 1.14.4+
lastFinishBreak = startBreak = System.currentTimeMillis();
lastFinishBreak = startBreak = player.now();
}
}
@@ -9,6 +9,7 @@ import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3i;
@@ -52,7 +53,7 @@ public class MultiBreak extends Check implements BlockBreakCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
hasBroken = false;
}
}
@@ -13,6 +13,7 @@ import ac.grim.grimac.utils.nmsutil.ReachUtils;
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.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
@@ -33,7 +34,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (!player.cameraEntity.isSelf())
if (player.gamemode == GameMode.SPECTATOR)
return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return; // falses
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // falses
@@ -49,7 +50,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
@Override
public void onPostFlyingBlockBreak(BlockBreak blockBreak) {
if (!player.cameraEntity.isSelf())
if (player.gamemode == GameMode.SPECTATOR)
return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return; // falses
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // falses
@@ -1,13 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "EntityPierce", configName = "EntityPierce", setback = 30)
public class EntityPierce extends Check implements PacketCheck {
public EntityPierce(GrimPlayer player) {
super(player);
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import java.util.ArrayList;
@@ -28,33 +29,30 @@ public class MultiInteractA extends Check implements PostPredictionCheck {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
int entity = packet.getEntityId();
boolean sneaking = packet.isSneaking().orElse(false);
onInteract(event, entity, sneaking);
if (hasInteracted && entity != lastEntity) {
String verbose = "lastEntity=" + lastEntity + ", entity=" + entity
+ ", lastSneaking=" + lastSneaking + ", sneaking=" + sneaking;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
lastEntity = entity;
lastSneaking = sneaking;
hasInteracted = true;
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
hasInteracted = false;
}
}
private void onInteract(PacketReceiveEvent event, int entity, boolean sneaking) {
if (hasInteracted && (entity != lastEntity || sneaking != lastSneaking)) {
String verbose = "lastEntity=" + lastEntity + ", entity=" + entity
+ ", lastSneaking=" + lastSneaking + ", sneaking=" + sneaking;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
lastEntity = entity;
lastSneaking = sneaking;
hasInteracted = true;
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (!player.canSkipTicks()) return;
@@ -8,6 +8,7 @@ import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@@ -17,7 +18,7 @@ import java.util.ArrayList;
public class MultiInteractB extends Check implements PostPredictionCheck {
private final ArrayList<String> flags = new ArrayList<>();
private Vector3f lastPos;
private boolean hasInteracted;
private boolean hasInteracted = false;
public MultiInteractB(final GrimPlayer player) {
super(player);
@@ -26,11 +27,11 @@ public class MultiInteractB extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
if (packet.getAction() != WrapperPlayClientInteractEntity.InteractAction.INTERACT_AT) return;
Vector3f pos = new WrapperPlayClientInteractEntity(event).getTarget().orElse(null);
Vector3f pos = packet.getTarget().orElse(null);
if (pos == null) return; // shouldn't ever happen, but whatever
if (pos == null) {
return;
}
if (hasInteracted && !pos.equals(lastPos)) {
String verbose = "pos=" + MessageUtil.toUnlabledString(pos) + ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
@@ -48,7 +49,7 @@ public class MultiInteractB extends Check implements PostPredictionCheck {
hasInteracted = true;
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
hasInteracted = false;
}
}
@@ -18,48 +18,31 @@ package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.api.config.ConfigManager;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.BlockHitData;
import ac.grim.grimac.utils.data.EntityHitData;
import ac.grim.grimac.utils.data.HitData;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.PacketEntitySizeable;
import ac.grim.grimac.utils.data.packetentity.dragon.PacketEntityEnderDragonPart;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
import ac.grim.grimac.utils.nmsutil.WorldRayTrace;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.component.ComponentTypes;
import com.github.retrooper.packetevents.protocol.component.builtin.item.ItemAttackRange;
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import com.viaversion.viaversion.api.Via;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
// You may not copy the check unless you are licensed under GPL
@CheckData(name = "Reach", setback = 10)
@@ -72,15 +55,9 @@ public class Reach extends Check implements PacketCheck {
private static final CheckResult NONE = new CheckResult(ResultType.NONE, "");
// Only one flag per reach attack, per entity, per tick.
// We store position because lastX isn't reliable on teleports.
private final Int2ObjectMap<InteractionData> playerAttackQueue = new Int2ObjectOpenHashMap<>();
// temporarily used to prevent falses in the wall hit check
private final Set<Vector3i> blocksChangedThisTick = new HashSet<>();
// extra distance to raytrace beyond player reach distance so we know how far beyond the legit distance a cheater hit
public static final double extraSearchDistance = 3;
private boolean ignoreNonPlayerTargets;
private final Int2ObjectMap<Vector3d> playerAttackQueue = new Int2ObjectOpenHashMap<>();
private boolean cancelImpossibleHits;
public double threshold;
private double threshold;
private double cancelBuffer; // For the next 4 hits after using reach, we aggressively cancel reach
public Reach(GrimPlayer player) {
@@ -90,117 +67,59 @@ public class Reach extends Check implements PacketCheck {
@Override
public void onPacketReceive(final PacketReceiveEvent event) {
if (!player.disableGrim && event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
onInteract(event, packet.getEntityId(), packet.getHand());
}
WrapperPlayClientInteractEntity action = new WrapperPlayClientInteractEntity(event);
// If the player set their look, or we know they have a new tick
final boolean isFlying = WrapperPlayClientPlayerFlying.isFlying(event.getPacketType());
if (isUpdate(event.getPacketType())) {
tickBetterReachCheckWithAngle(isFlying);
}
}
private void onInteract(PacketReceiveEvent event, int entityId, InteractionHand hand) {
// Don't let the player teleport to bypass reach
if (player.getSetbackTeleportUtil().shouldBlockMovement()) {
event.setCancelled(true);
player.onPacketCancel();
return;
}
PacketEntity entity = player.compensatedEntities.entityMap.get(entityId);
// Stop people from freezing transactions before an entity spawns to bypass reach
// TODO: implement dragon parts?
if (entity == null || entity instanceof PacketEntityEnderDragonPart) {
// Only cancel if and only if we are tracking this entity
// This is because we don't track paintings.
if (shouldModifyPackets() && player.compensatedEntities.serverPositionsMap.containsKey(entityId)) {
// Don't let the player teleport to bypass reach
if (player.getSetbackTeleportUtil().shouldBlockMovement()) {
event.setCancelled(true);
player.onPacketCancel();
}
return;
}
if (ignoreNonPlayerTargets && !entity.type.equals(EntityTypes.PLAYER)) {
return;
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
// TODO: Remove when in front of via
if (entity.type == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8))
return;
// Prevents Happy Ghast Reach false on 1.21.6+ servers with ViaBackwards set up
if (entity.type == EntityTypes.HAPPY_GHAST && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_6))
return;
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR)
return;
if (player.inVehicle()) return;
if (entity.riding != null) return;
ItemStack currentStack = player.inventory.getItemInHand(hand);
ItemStack startStack = player.inventory.getStartOfTickStack();
boolean hasRange = false;
float maxReach = 0f;
float hitboxMargin = 0f;
boolean clientAttackRangeExists = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11);
boolean clientAndServerAgrees = clientAttackRangeExists && ATTACK_RANGE_COMPONENT_EXISTS;
boolean viaVersionAvailable = false;
if (USE_1_8_HITBOX_MARGIN && ViaVersionUtil.isAvailable) {
viaVersionAvailable = Via.getConfig().getValues().containsKey("use-1_8-hitbox-margin") && Via.getConfig().use1_8HitboxMargin();
}
boolean clientAndViaVersion = clientAttackRangeExists && viaVersionAvailable;
if (clientAndServerAgrees || clientAndViaVersion) {
ItemAttackRange startRange = startStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
ItemAttackRange currentRange = currentStack.getComponentOr(ComponentTypes.ATTACK_RANGE, null);
if (clientAndViaVersion) {
if (startStack != ItemStack.EMPTY) {
startRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
}
if (currentStack != ItemStack.EMPTY) {
currentRange = new ItemAttackRange(0F, 3F, 0F, 4F, 0.1F, 1F);
PacketEntity entity = player.compensatedEntities.entityMap.get(action.getEntityId());
// Stop people from freezing transactions before an entity spawns to bypass reach
// TODO: implement dragon parts?
if (entity == null || entity instanceof PacketEntityEnderDragonPart) {
// Only cancel if and only if we are tracking this entity
// This is because we don't track paintings.
if (shouldModifyPackets() && player.compensatedEntities.serverPositionsMap.containsKey(action.getEntityId())) {
event.setCancelled(true);
player.onPacketCancel();
}
return;
}
// If the start stack has no range component, the client defaults to vanilla reach behavior,
// regardless of what the current stack is (No Range -> X = No Range used).
if (startRange != null) {
hasRange = true;
if (currentRange == null) {
// Range (Start) -> No Range (Current)
// Client logic uses Start Range
maxReach = startRange.getMaxRange();
hitboxMargin = startRange.getHitboxMargin();
} else {
// Range (Start) -> Range (Current)
// Client logic requires satisfying BOTH constraints
maxReach = Math.min(startRange.getMaxRange(), currentRange.getMaxRange());
hitboxMargin = Math.min(startRange.getHitboxMargin(), currentRange.getHitboxMargin());
}
// Dead entities cause false flags (https://github.com/GrimAnticheat/Grim/issues/546)
if (entity.isDead) return;
// TODO: Remove when in front of via
if (entity.type == EntityTypes.ARMOR_STAND && player.getClientVersion().isOlderThan(ClientVersion.V_1_8))
return;
//Prevents Happy Ghast Reach false on 1.21.6+ servers with ViaBackwards set up
if (entity.type == EntityTypes.HAPPY_GHAST && player.getClientVersion().isOlderThan(ClientVersion.V_1_21_6)) {
return;
}
if (player.gamemode == GameMode.CREATIVE || player.gamemode == GameMode.SPECTATOR)
return;
if (player.inVehicle()) return;
if (entity.riding != null) return;
boolean tooManyAttacks = playerAttackQueue.size() > 10;
if (!tooManyAttacks) {
playerAttackQueue.put(action.getEntityId(), new Vector3d(player.x, player.y, player.z)); // Queue for next tick for very precise check
}
boolean knownInvalid = isKnownInvalid(entity);
if ((shouldModifyPackets() && cancelImpossibleHits && knownInvalid) || tooManyAttacks) {
event.setCancelled(true);
player.onPacketCancel();
}
}
boolean tooManyAttacks = playerAttackQueue.size() > 10;
if (!tooManyAttacks) {
playerAttackQueue.put(entityId, new InteractionData(
player.x, player.y, player.z,
hasRange, maxReach, hitboxMargin
)); // Queue for next tick for very precise check
}
boolean knownInvalid = isKnownInvalid(entity, hasRange, maxReach, hitboxMargin);
if ((shouldModifyPackets() && cancelImpossibleHits && knownInvalid) || tooManyAttacks) {
event.setCancelled(true);
player.onPacketCancel();
// If the player set their look, or we know they have a new tick
if (isUpdate(event.getPacketType())) {
tickBetterReachCheckWithAngle();
}
}
@@ -212,7 +131,7 @@ public class Reach extends Check implements PacketCheck {
// than this method. If this method flags, the other method WILL flag.
//
// Meaning that the other check should be the only one that flags.
private boolean isKnownInvalid(PacketEntity reachEntity, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin) {
private boolean isKnownInvalid(PacketEntity reachEntity) {
// If the entity doesn't exist, or if it is exempt, or if it is dead
if ((blacklisted.contains(reachEntity.type) || !reachEntity.isLivingEntity) && reachEntity.type != EntityTypes.END_CRYSTAL)
return false; // exempt
@@ -223,23 +142,23 @@ public class Reach extends Check implements PacketCheck {
// Filter out what we assume to be cheats
if (cancelBuffer != 0) {
CheckResult result = checkReach(reachEntity, player.x, player.y, player.z, hasAttackRange, itemMaxReach, itemHitboxMargin, true);
CheckResult result = checkReach(reachEntity, new Vector3d(player.x, player.y, player.z), true);
return result.isFlag(); // If they flagged
} else {
SimpleCollisionBox targetBox = getTargetBox(reachEntity);
double maxReach = applyReachModifiers(targetBox, hasAttackRange, itemMaxReach, itemHitboxMargin, !player.packetStateData.didLastMovementIncludePosition);
return ReachUtils.getMinReachToBox(player, targetBox) > maxReach;
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) {
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);
}
}
private void tickBetterReachCheckWithAngle(boolean isFlying) {
for (Int2ObjectMap.Entry<InteractionData> attack : playerAttackQueue.int2ObjectEntrySet()) {
private void tickBetterReachCheckWithAngle() {
for (Int2ObjectMap.Entry<Vector3d> attack : playerAttackQueue.int2ObjectEntrySet()) {
PacketEntity reachEntity = player.compensatedEntities.entityMap.get(attack.getIntKey());
if (reachEntity == null) continue;
InteractionData interactionData = attack.getValue();
CheckResult result = checkReach(reachEntity, interactionData.x, interactionData.y, interactionData.z, interactionData.hasAttackRange, interactionData.maxReach, interactionData.hitboxMargin, false);
CheckResult result = checkReach(reachEntity, attack.getValue(), false);
switch (result.type()) {
case REACH -> {
String added = ", type=" + reachEntity.type.getName().getKey();
@@ -255,43 +174,64 @@ public class Reach extends Check implements PacketCheck {
}
player.checkManager.getCheck(Hitboxes.class).flagAndAlert(result.verbose() + added);
}
case WALL_HIT -> {
String added = reachEntity.type == EntityTypes.PLAYER ? "" : "type=" + reachEntity.type.getName().getKey();
player.checkManager.getCheck(WallHit.class).flagAndAlert(result.verbose() + added);
}
case ENTITY_PIERCE -> {
String added = reachEntity.type == EntityTypes.PLAYER ? "" : "type=" + reachEntity.type.getName().getKey();
player.checkManager.getCheck(EntityPierce.class).flagAndAlert(result.verbose() + added);
}
}
}
playerAttackQueue.clear();
// We can't use transactions for this because of this problem:
// transaction -> block changed applied -> 2nd transaction -> list cleared -> attack packet -> flying -> reach block hit checked, falses
if (isFlying) blocksChangedThisTick.clear();
}
@NotNull
private CheckResult checkReach(PacketEntity reachEntity, double x, double y, double z, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin, boolean isPrediction) {
SimpleCollisionBox targetBox = getTargetBox(reachEntity);
private CheckResult checkReach(PacketEntity reachEntity, Vector3d from, boolean isPrediction) {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
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)
// Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
targetBox.expand(0.1f);
}
targetBox.expand(threshold);
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
//
// Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement
// However, on 1.21.2+ we do know if they had 0.03 movement
if (!player.packetStateData.didLastLastMovementIncludePosition || player.canSkipTicks())
targetBox.expand(player.getMovementThreshold());
double maxReach = applyReachModifiers(targetBox, hasAttackRange, itemMaxReach, itemHitboxMargin, !player.packetStateData.didLastLastMovementIncludePosition);
double minDistance = Double.MAX_VALUE;
// will store all lookVecsAndEyeHeight pairs that landed a hit on the target entity
// We only need to check for blocking intersections for these
List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights = new ArrayList<>();
// https://bugs.mojang.com/browse/MC-67665
List<Vector3dm> possibleLookDirs = new ArrayList<>(Collections.singletonList(ReachUtils.getLook(player, player.yaw, player.pitch)));
// If we are a tick behind, we don't know their next look so don't bother doing this
if (!isPrediction) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastYaw, player.pitch));
// 1.9+ players could be a tick behind because we don't get skipped ticks
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
possibleLookDirs.add(ReachUtils.getLook(player, player.lastYaw, player.lastPitch));
}
// 1.7 players do not have any of these issues! They are always on the latest look vector
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_8)) {
possibleLookDirs = Collections.singletonList(ReachUtils.getLook(player, player.yaw, player.pitch));
}
}
final double maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final Vector3dm[] possibleLookDirs = player.getPossibleLookVectors(isPrediction);
final Vector3dm eyePos = new Vector3dm(x, 0, z);
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
for (Vector3dm lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
eyePos.setY(y + eye);
eyePos.setY(from.getY() + eye);
Vector3dm endReachPos = eyePos.clone().add(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance);
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
@@ -303,44 +243,13 @@ public class Reach extends Check implements PacketCheck {
if (intercept != null) {
minDistance = Math.min(eyePos.distance(intercept), minDistance);
lookVecsAndEyeHeights.add(new Pair<>(lookVec, eye));
}
}
}
if (hitboxDebuggingEnabled())
sendHitboxDebugData(reachEntity, x, y, z, lookVecsAndEyeHeights, isPrediction);
HitData foundHitData = null;
// If the entity is within range of the player (we'll flag anyway if not, so no point checking blocks in this case)
// Ignore when could be hitting through a moving shulker, piston blocks. They are just too glitchy/uncertain to check.
if (minDistance <= distance - extraSearchDistance && !player.compensatedWorld.isNearHardEntity(player.boundingBox.copy().expand(4))) {
// we can optimize didRayTraceHit more to only rayTrace up to the maximize distance of all rays that hit to the target...
// I'm too lazy to do that and we don't need to optimize that much yet so...
final @Nullable Pair<Double, HitData> hitResult = WorldRayTrace.didRayTraceHit(player, reachEntity, lookVecsAndEyeHeights, x, y, z);
HitData hitData = hitResult.second();
// If the returned hit result was NOT the target entity we flag the check
if (hitData instanceof EntityHitData &&
player.compensatedEntities.getPacketEntityID(((EntityHitData) hitData).getEntity()) != player.compensatedEntities.getPacketEntityID(reachEntity)) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
// until I fix block modeling exempt any blocks changed this tick
} else if (hitData instanceof BlockHitData && !blocksChangedThisTick.contains(((BlockHitData) hitData).position())) {
minDistance = Double.MIN_VALUE;
foundHitData = hitData;
}
}
// if the entity is not exempt and the entity is alive
if ((!blacklisted.contains(reachEntity.type) && reachEntity.isLivingEntity) || reachEntity.type == EntityTypes.END_CRYSTAL) {
if (minDistance == Double.MIN_VALUE && foundHitData != null) {
cancelBuffer = 1;
if (foundHitData instanceof BlockHitData) {
return new CheckResult(ResultType.WALL_HIT, "Hit block=" + ((BlockHitData) foundHitData).state().getType().getName() + " ");
} else { // entity hit data
return new CheckResult(ResultType.ENTITY_PIERCE, "Hit entity=" + ((EntityHitData) foundHitData).getEntity().type.getName() + " ");
}
} else if (minDistance == Double.MAX_VALUE) {
if (minDistance == Double.MAX_VALUE) {
cancelBuffer = 1;
return new CheckResult(ResultType.HITBOX, "");
} else if (minDistance > maxReach) {
@@ -354,55 +263,14 @@ public class Reach extends Check implements PacketCheck {
return NONE;
}
private SimpleCollisionBox getTargetBox(PacketEntity reachEntity) {
if (reachEntity.type == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
return new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
}
return reachEntity.getPossibleCollisionBoxes();
}
private static final boolean ATTACK_RANGE_COMPONENT_EXISTS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_11);
private static final boolean USE_1_8_HITBOX_MARGIN = PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_8_8);
private double applyReachModifiers(SimpleCollisionBox targetBox, boolean hasAttackRange, float itemMaxReach, float itemHitboxMargin, boolean giveMovementThreshold) {
double maxReach;
double hitboxMargin = threshold;
if (hasAttackRange) {
maxReach = itemMaxReach;
hitboxMargin += itemHitboxMargin;
} else {
maxReach = player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
// Yes, this is vanilla and not uncertainty. All reach checks have this or they are wrong.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
hitboxMargin += 0.1f;
}
}
// This is better than adding to the reach, as 0.03 can cause a player to miss their target
// Adds some more than 0.03 uncertainty in some cases, but a good trade off for simplicity
//
// Just give the uncertainty on 1.9+ clients as we have no way of knowing whether they had 0.03 movement
// However, on 1.21.2+ we do know if they had 0.03 movement
if (giveMovementThreshold || player.canSkipTicks()) {
hitboxMargin += player.getMovementThreshold();
}
targetBox.expand(hitboxMargin);
return maxReach;
}
@Override
public void onReload(ConfigManager config) {
this.ignoreNonPlayerTargets = config.getBooleanElse("Reach.ignore-non-player-targets", false);
this.cancelImpossibleHits = config.getBooleanElse("Reach.block-impossible-hits", true);
this.threshold = config.getDoubleElse("Reach.threshold", 0.0005);
}
private enum ResultType {
REACH, HITBOX, WALL_HIT, ENTITY_PIERCE, NONE
REACH, HITBOX, NONE
}
private record CheckResult(ResultType type, String verbose) {
@@ -410,71 +278,4 @@ public class Reach extends Check implements PacketCheck {
return type != ResultType.NONE;
}
}
public void handleBlockChange(Vector3i vector3i, WrappedBlockState state) {
if (blocksChangedThisTick.size() >= 40) return; // Don't let players freeze movement packets to grow this
// Only do this for nearby blocks
if (GrimMath.distanceSquared(vector3i.x, vector3i.y, vector3i.z, player.x, player.y, player.z) > 36) return;
// Only do this if the state really had any world impact
if (state.equals(player.compensatedWorld.getBlock(vector3i))) return;
blocksChangedThisTick.add(vector3i);
}
private boolean hitboxDebuggingEnabled() {
return player.checkManager.getCheck(HitboxDebugHandler.class).isEnabled();
}
private void sendHitboxDebugData(PacketEntity reachEntity, double x, double y, double z, List<Pair<Vector3dm, Double>> lookVecsAndEyeHeights, boolean isPrediction) {
Map<Integer, CollisionBox> hitboxes = new HashMap<>();
for (Int2ObjectMap.Entry<PacketEntity> entry : player.compensatedEntities.entityMap.int2ObjectEntrySet()) {
PacketEntity entity = entry.getValue();
if (!entity.canHit()) continue;
CollisionBox box;
if (entity.equals(reachEntity)) {
// Target entity gets expanded hitbox
box = entity.getPossibleCollisionBoxes();
SimpleCollisionBox sBox = (SimpleCollisionBox) box;
sBox.expand(threshold);
// Add movement threshold uncertainty for 1.9+ or non-position updates
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(player.getMovementThreshold());
}
} else {
// Non-target entities
box = entity.getMinimumPossibleCollisionBoxes();
if (box instanceof NoCollisionBox) {
hitboxes.put(entry.getIntKey(), NoCollisionBox.INSTANCE);
continue;
} else if (box instanceof SimpleCollisionBox sBox) {
sBox.expand(-threshold);
// Shrink non-target entities by movement threshold when applicable
if (!player.packetStateData.didLastLastMovementIncludePosition
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
sBox.expand(-player.getMovementThreshold());
}
}
}
// Add 1.8 and below extra hitbox size
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& box instanceof SimpleCollisionBox) {
((SimpleCollisionBox) box).expand(0.1f);
}
hitboxes.put(entry.getIntKey(), box);
}
player.checkManager.getCheck(HitboxDebugHandler.class).sendHitboxData(hitboxes,
Collections.singleton(player.compensatedEntities.getPacketEntityID(reachEntity)),
lookVecsAndEyeHeights,
x, y, z,
isPrediction, player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE));
}
private record InteractionData(double x, double y, double z, boolean hasAttackRange, float maxReach, float hitboxMargin) {
}
}
@@ -1,34 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "SelfInteract", description = "Interacted with self")
public class SelfInteract extends Check implements PacketCheck {
public SelfInteract(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
onInteract(event, packet.getEntityId());
}
}
// TODO: should check for camera entity id instead of player entity id?
private void onInteract(PacketReceiveEvent event, int entityId) {
if (player.cameraEntity.isSelf() && entityId == player.entityID
&& flagAndAlert() && shouldModifyPackets()) { // Instant ban
event.setCancelled(true);
player.onPacketCancel();
}
}
}
@@ -1,13 +0,0 @@
package ac.grim.grimac.checks.impl.combat;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "WallHit", configName = "WallHit", setback = 20)
public class WallHit extends Check implements PacketCheck {
public WallHit(GrimPlayer player) {
super(player);
}
}
@@ -23,7 +23,7 @@ public class CrashC extends Check implements PacketCheck {
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());
flagAndAlert("xyzYP: " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ", " + pos.getYaw() + ", " + pos.getPitch());
player.getSetbackTeleportUtil().executeViolationSetback();
event.setCancelled(true);
player.onPacketCancel();
@@ -6,7 +6,9 @@ import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraA", description = "Started gliding while already gliding")
public class ElytraA extends Check implements PostPredictionCheck {
@@ -31,6 +33,13 @@ public class ElytraA extends Check implements PostPredictionCheck {
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_15) && event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION
&& new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_FLYING_WITH_ELYTRA
) onStartGliding(event);
}
@Override
public void onPredictionComplete(PredictionComplete predictionComplete) {
if (setback) {
@@ -8,6 +8,7 @@ import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraC", description = "Started gliding too frequently")
@@ -26,7 +27,7 @@ public class ElytraC extends Check implements PostPredictionCheck {
return;
}
if (!player.cameraEntity.isSelf()) {
if (player.gamemode == GameMode.SPECTATOR) {
glideThisTick = glideLastTick = false;
}
@@ -1,42 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity.InteractAction;
@CheckData(name = "InventoryA", setback = 3, description = "Attacked an entity while inventory is open")
public class InventoryA extends InventoryCheck {
public InventoryA(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
if (wrapper.getAction() != InteractAction.ATTACK) return;
// Is not possible to attack while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission())
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -1,35 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "InventoryB", setback = 3, description = "Started digging blocks while inventory is open")
public class InventoryB extends InventoryCheck {
public InventoryB(GrimPlayer player) {
super(player);
}
public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging wrapper) {
if (wrapper.getAction() != DiggingAction.START_DIGGING) return;
// Is not possible to start digging a block while the inventory is open.
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -1,30 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
@CheckData(name = "InventoryC", setback = 3, description = "Placed a block while inventory is open")
public class InventoryC extends InventoryCheck {
public InventoryC(GrimPlayer player) {
super(player);
}
public void onBlockPlace(final BlockPlace place) {
// It is not possible to place a block while the inventory is open
if (player.hasInventoryOpen) {
if (flagAndAlert()) {
if (shouldModifyPackets()) {
place.resync();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@@ -1,85 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VectorData.MoveVectorData;
import ac.grim.grimac.utils.data.VehicleData;
import java.util.StringJoiner;
@CheckData(name = "InventoryD", setback = 1, decay = 0.25)
public class InventoryD extends InventoryCheck {
private int horseJumpVerbose;
public InventoryD(GrimPlayer player) {
super(player);
}
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
if (!predictionComplete.isChecked() ||
predictionComplete.getData().isTeleport() ||
player.getSetbackTeleportUtil().blockOffsets ||
player.packetStateData.lastPacketWasTeleport ||
player.packetStateData.isSlowedByUsingItem() ||
System.currentTimeMillis() - player.lastBlockPlaceUseItem < 50L) {
return;
}
if (player.hasInventoryOpen) {
boolean inVehicle = player.inVehicle();
boolean isJumping, isMoving;
if (inVehicle) {
VehicleData vehicle = player.vehicleData;
// Will flag once if player open anything with pressed space bar
isJumping = !vehicle.pendingJumps.isEmpty() && horseJumpVerbose++ >= 1;
isMoving = vehicle.nextVehicleForward != 0 || vehicle.nextVehicleHorizontal != 0;
} else {
MoveVectorData move = findMovement(player.predictedVelocity);
isJumping = player.predictedVelocity.isJump();
isMoving = move != null && (move.x != 0 || move.z != 0);
}
if (!isMoving && !isJumping) {
reward();
return;
}
if (flag()) {
if (!isNoSetbackPermission())
closeInventory();
StringJoiner joiner = new StringJoiner(" ");
if (isMoving) joiner.add("moving");
if (isJumping) joiner.add("jumping");
if (inVehicle) joiner.add("inVehicle");
alert(joiner.toString());
}
} else {
horseJumpVerbose = 0;
}
}
private MoveVectorData findMovement(VectorData vectorData) {
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
while (vectorData != null) {
vectorData = vectorData.lastVector;
if (vectorData instanceof MoveVectorData) {
return (MoveVectorData) vectorData;
}
}
return null;
}
}
@@ -1,50 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "InventoryE", setback = 3, description = "Sent a held item change packet while inventory is open")
public class InventoryE extends InventoryCheck {
private long lastTransaction = Long.MAX_VALUE; // Impossible transaction ID
public InventoryE(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
// It is not possible to change hotbar slots with held item change while the inventory is open
// A container click packet would be sent instead
if (player.hasInventoryOpen) {
if (this.lastTransaction < player.lastTransactionReceived.get()
&& flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.inventory.needResend = true;
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.HELD_ITEM_CHANGE) {
this.lastTransaction = player.lastTransactionSent.get();
}
}
}
@@ -1,46 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.inventory.InventoryDesyncStatus;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
@CheckData(name = "InventoryF", setback = 3, description = "Sent a click window packet without a open inventory", experimental = true)
public class InventoryF extends InventoryCheck {
public InventoryF(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
// Exempt on 1.9+ server version due to the Via hack done in PacketPlayerWindow, the exemption can be deleted
// once we are ahead of ViaVersion
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)
|| player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
if (!player.hasInventoryOpen && player.inventoryDesyncStatus == InventoryDesyncStatus.NOT_DESYNCED) {
if (flagAndAlert()) {
// Cancel the packet
if (shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (!isNoSetbackPermission()) {
closeInventory();
}
}
} else {
reward();
}
}
}
}
@@ -1,40 +0,0 @@
package ac.grim.grimac.checks.impl.inventory;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.InventoryCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "InventoryG", setback = 3, description = "Sent a entity action packet while inventory is open", experimental = true)
public class InventoryG extends InventoryCheck {
public InventoryG(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.lastPacketWasTeleport) return;
super.onPacketReceive(event);
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
WrapperPlayClientEntityAction.Action action = wrapper.getAction();
if (action == WrapperPlayClientEntityAction.Action.STOP_SNEAKING
|| action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
return;
}
if (player.hasInventoryOpen) {
if (flagAndAlert() && !isNoSetbackPermission()) {
closeInventory();
}
} else {
reward();
}
}
}
}
@@ -41,6 +41,10 @@ public class GhostBlockMitigation extends BlockPlaceCheck {
continue;
}
if (i == xAgainst && j == yAgainst && k == zAgainst) {
continue;
}
if (!world.isChunkLoaded(i >> 4, k >> 4)) {
continue;
}
@@ -21,7 +21,15 @@ import java.util.ArrayDeque;
import java.util.List;
import java.util.Locale;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.*;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.ANIMATION;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.CLICK_WINDOW;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.ENTITY_ACTION;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.HELD_ITEM_CHANGE;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.INTERACT_ENTITY;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_ABILITIES;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.PLAYER_DIGGING;
import static com.github.retrooper.packetevents.protocol.packettype.PacketType.Play.Client.USE_ITEM;
@CheckData(name = "Post")
public class Post extends Check implements PacketCheck, PostPredictionCheck {
@@ -99,7 +107,6 @@ public class Post extends Check implements PacketCheck, PostPredictionCheck {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19_3) && player.inVehicle()) {
return;
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.LEAVE_BED) return;
if (sentFlying) post.add(event.getPacketType());
}
}
@@ -6,10 +6,8 @@ import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "MultiActionsA", description = "Attacked while using an item", experimental = true)
public class MultiActionsA extends Check implements PacketCheck {
@@ -19,9 +17,8 @@ public class MultiActionsA extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.itemInUseHand == InteractionHand.OFF_HAND)) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK
|| event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING && new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.STAB) {
if (player.packetStateData.isSlowedByUsingItem() && (player.packetStateData.lastSlotSelected == player.packetStateData.getSlowedByUsingItemSlot() || player.packetStateData.itemInUseHand == InteractionHand.OFF_HAND) && event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -39,15 +39,12 @@ public class MultiActionsC extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() != PacketType.Play.Client.CLICK_WINDOW) return;
if (player.serverOpenedInventoryThisTick) return;
String verbose = getVerbose(player);
if (verbose.isEmpty()) return;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW && !player.serverOpenedInventoryThisTick) {
String verbose = getVerbose(player);
if (!verbose.isEmpty() && flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
@@ -15,15 +15,12 @@ public class MultiActionsD extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() != PacketType.Play.Client.CLOSE_WINDOW) return;
if (player.serverOpenedInventoryThisTick) return;
String verbose = MultiActionsC.getVerbose(player);
if (verbose.isEmpty()) return;
// Don't cancel this packet, because it won't do anything except for making chests
// look like they are still open (desynced),
// and it can cause incompatibility issues with plugins
flagAndAlert(verbose);
if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
String verbose = MultiActionsC.getVerbose(player);
if (!verbose.isEmpty() && flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
@@ -9,12 +9,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.InteractionHand;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientAnimation;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@CheckData(name = "PacketOrderB", description = "Did not swing for attack")
public class PacketOrderB extends Check implements PacketCheck {
@@ -46,8 +41,7 @@ public class PacketOrderB extends Check implements PacketCheck {
public void onPacketReceive(PacketReceiveEvent event) {
if (exempt) return;
if (event.getPacketType() == PacketType.Play.Client.ANIMATION
&& new WrapperPlayClientAnimation(event).getHand() == InteractionHand.MAIN_HAND) {
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
sentAnimationSinceLastAttack = sentAnimation = true;
sentAttack = sentSlotSwitch = false;
return;
@@ -56,15 +50,17 @@ public class PacketOrderB extends Check implements PacketCheck {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
if (packet.getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
onAttack(event);
return;
}
}
sentAttack = true;
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
if (packet.getAction() == DiggingAction.STAB) {
onAttack(event);
if (is1_9 ? !sentAnimationSinceLastAttack : !sentAnimation) {
sentAttack = false; // don't flag twice
if (flagAndAlert("pre-attack") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentAnimationSinceLastAttack = sentAnimation = sentSlotSwitch = false;
return;
}
}
@@ -82,20 +78,4 @@ public class PacketOrderB extends Check implements PacketCheck {
sentAttack = sentAnimation = sentSlotSwitch = false;
}
}
private void onAttack(PacketReceiveEvent event) {
if (player.gamemode == GameMode.SPECTATOR && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_11)) return;
sentAttack = true;
if (is1_9 ? !sentAnimationSinceLastAttack : !sentAnimation) {
sentAttack = false; // don't flag twice
if (flagAndAlert("pre-attack") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentAnimationSinceLastAttack = sentAnimation = sentSlotSwitch = false;
}
}
@@ -15,7 +15,8 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPl
@CheckData(name = "PacketOrderC")
public class PacketOrderC extends Check implements PacketCheck {
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10); // 1.7 players do not send INTERACT_AT
// 1.7 players do not send INTERACT_AT, so we cannot check them
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10);
private boolean sentInteractAt = false;
private int requiredEntity;
private InteractionHand requiredHand;
@@ -39,16 +39,12 @@ public class PacketOrderD extends Check implements PacketCheck {
}
}
sentMainhand = false;
}
if (action == InteractAction.INTERACT_AT) {
if (sneaking != requiredSneaking || entity != requiredEntity) {
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else if (sneaking != requiredSneaking || entity != requiredEntity) {
String verbose = "requiredEntity=" + requiredEntity + ", entity=" + entity
+ ", requiredSneaking=" + requiredSneaking + ", sneaking=" + sneaking;
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
} else {
@@ -22,7 +22,7 @@ public class PacketOrderE extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.HELD_ITEM_CHANGE) {
if (player.packetOrderProcessor.isAttackingOrStabbing()
if (player.packetOrderProcessor.isAttacking()
|| player.packetOrderProcessor.isRightClicking()
|| player.packetOrderProcessor.isOpeningInventory()
|| player.packetOrderProcessor.isReleasing()
@@ -32,7 +32,7 @@ public class PacketOrderE extends Check implements PostPredictionCheck {
|| player.packetOrderProcessor.isStartingToGlide()
|| player.packetOrderProcessor.isJumpingWithMount()
) {
String verbose = "attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
String verbose = "attacking=" + player.packetOrderProcessor.isAttacking()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", openingInventory=" + player.packetOrderProcessor.isOpeningInventory()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging;
@@ -41,7 +42,7 @@ public class PacketOrderF extends Check implements PostPredictionCheck {
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING
&& !canCancel(new WrapperPlayClientPlayerDigging(event).getAction())
&& new WrapperPlayClientPlayerDigging(event).getAction() == DiggingAction.RELEASE_USE_ITEM
) return; // don't cause a noslow
event.setCancelled(true);
@@ -28,26 +28,27 @@ public class PacketOrderG extends Check implements PostPredictionCheck {
DiggingAction action = null;
if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
action = new WrapperPlayClientPlayerDigging(event).getAction();
if (action != DiggingAction.SWAP_ITEM_WITH_OFFHAND
&& action != DiggingAction.DROP_ITEM
&& action != DiggingAction.DROP_ITEM_STACK
if (action == DiggingAction.RELEASE_USE_ITEM
|| action == DiggingAction.START_DIGGING
|| action == DiggingAction.CANCELLED_DIGGING
|| action == DiggingAction.FINISHED_DIGGING
) return;
}
if (player.packetOrderProcessor.isAttackingOrStabbing()
if (player.packetOrderProcessor.isAttacking()
|| player.packetOrderProcessor.isReleasing()
|| player.packetOrderProcessor.isRightClicking()
|| player.packetOrderProcessor.isPicking()
|| player.packetOrderProcessor.isDigging()
) {
String verbose = "action=" + (action == null ? "openInventory" : action == DiggingAction.SWAP_ITEM_WITH_OFFHAND ? "swap" : "drop")
+ ", attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
+ ", attacking=" + player.packetOrderProcessor.isAttacking()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets() && canCancel(action)) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
@@ -32,7 +32,20 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
if (new WrapperPlayClientInteractEntity(event).getAction() == WrapperPlayClientInteractEntity.InteractAction.ATTACK) {
onAttack(event);
if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=attack, rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
} else if (player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=interact, releasing=" + player.packetOrderProcessor.isReleasing() + ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
@@ -64,12 +77,9 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
switch (packet.getAction()) {
case STAB:
onAttack(event);
break;
case RELEASE_USE_ITEM:
if (player.packetOrderProcessor.isAttackingOrStabbing() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttackingOrStabbing()
if (player.packetOrderProcessor.isAttacking() || player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=release, attacking=" + player.packetOrderProcessor.isAttacking()
+ ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", digging=" + player.packetOrderProcessor.isDigging();
@@ -96,7 +106,7 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
}
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
digging = false;
}
}
@@ -124,23 +134,6 @@ public class PacketOrderI extends Check implements PostPredictionCheck {
setback = false;
}
private void onAttack(PacketReceiveEvent event) {
if (player.packetOrderProcessor.isRightClicking() || player.packetOrderProcessor.isPicking() || player.packetOrderProcessor.isReleasing() || player.packetOrderProcessor.isDigging()) {
String verbose = "type=attack" + ", rightClicking=" + player.packetOrderProcessor.isRightClicking()
+ ", picking=" + player.packetOrderProcessor.isPicking()
+ ", releasing=" + player.packetOrderProcessor.isReleasing()
+ ", digging=" + player.packetOrderProcessor.isDigging();
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else {
flags.add(verbose);
}
}
}
@Override
public void onReload(ConfigManager config) {
exemptPlacingWhileDigging = config.getBooleanElse(getConfigName() + ".exempt-placing-while-digging", false);
@@ -19,7 +19,6 @@ public class PacketOrderJ extends Check implements PostPredictionCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_BLOCK_PLACEMENT || event.getPacketType() == PacketType.Play.Client.USE_ITEM) {
// we don't check stabbing here because you don't need to target an entity to stab
if (player.packetOrderProcessor.isAttacking() && !player.packetOrderProcessor.isInteracting()) {
if (!player.canSkipTicks()) {
if (flagAndAlert() && shouldModifyPackets()) {
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
@@ -48,7 +49,7 @@ public class PacketOrderM extends Check implements PostPredictionCheck {
interacting = false;
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
usingWithoutInteract = interacting = false;
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.anticheat.update.BlockPlace;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerBlockPlacement;
@@ -45,7 +46,7 @@ public class PacketOrderN extends BlockPlaceCheck {
placing = false;
}
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
usingWithoutPlacing = placing = false;
}
}
@@ -8,6 +8,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.wrapper.play.client.*;
import lombok.Getter;
@@ -38,7 +39,6 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
private boolean leavingBed;
private boolean startingToGlide;
private boolean jumpingWithMount;
private boolean stabbing;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
@@ -64,7 +64,6 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
case DROP_ITEM, DROP_ITEM_STACK -> dropping = true;
case RELEASE_USE_ITEM -> releasing = true;
case FINISHED_DIGGING, CANCELLED_DIGGING, START_DIGGING -> digging = true;
case STAB -> stabbing = true;
}
}
@@ -112,7 +111,7 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
closingInventory = true;
}
if (!player.cameraEntity.isSelf() || isTickPacket(packetType)
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(packetType)
|| player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)
&& !player.compensatedWorld.isChunkLoaded(GrimMath.floor(player.x) >> 4, GrimMath.floor(player.z) >> 4)) {
openingInventory = false;
@@ -134,7 +133,6 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
leavingBed = false;
startingToGlide = false;
jumpingWithMount = false;
stabbing = false;
}
}
@@ -142,9 +140,4 @@ public final class PacketOrderProcessor extends Check implements PacketCheck {
public boolean isRightClicking() {
return placing || using || interacting;
}
@Contract(pure = true)
public boolean isAttackingOrStabbing() {
return attacking || stabbing;
}
}
@@ -133,14 +133,8 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
}
@Override
public boolean toggleListener(GrimPlayer player) {
boolean wasPresent = listeners.remove(player);
if (wasPresent) {
return false;
} else {
listeners.add(player);
return true;
}
public void toggleListener(GrimPlayer player) {
if (!listeners.remove(player)) listeners.add(player);
}
@Override
@@ -10,29 +10,6 @@ import com.github.retrooper.packetevents.util.Vector3f;
@CheckData(name = "FabricatedPlace", description = "Sent out of bounds cursor position")
public class FabricatedPlace extends BlockPlaceCheck {
/**
* MAX_DOUBLE_ERROR:
* Represents the maximum possible floating-point arithmetic error that can occur
* when calculating vectors at the Minecraft World Border (30,000,000 blocks).
*
* Math.ulp(30,000,000.0) is approx 3.72E-9.
* We multiply by 2.0 to account for subtraction compounding logic in raytracing.
*
* This constant is safe to use everywhere; 7.450580596923828E-9 is physically indistinguishable from zero.
*/
private static final double MAX_DOUBLE_ERROR = Math.ulp(30_000_000.0) * 2.0;
/**
* FLOAT_STEP_AT_ONE:
* Represents the resolution of a Float at the value 1.0 (and 1.5).
* Math.ulp(1.0f) is approx 1.1920929E-7.
*
* When checking the upper bound (1.0), we must allow this variance because a Double
* slightly larger than 1.0 might get cast/snapped to the next available Float step.
*/
private static final double FLOAT_STEP_AT_ONE = Math.ulp(1.0f);
public FabricatedPlace(GrimPlayer player) {
super(player);
}
@@ -42,50 +19,11 @@ public class FabricatedPlace extends BlockPlaceCheck {
Vector3f cursor = place.cursor;
if (cursor == null) return;
// Determine if we allow up to 1.5 (Lecterns, Scaffolding, etc)
boolean isExtended = Materials.isShapeExceedsCube(place.getPlacedAgainstMaterial())
|| place.getPlacedAgainstMaterial() == StateTypes.LECTERN;
double allowed = Materials.isShapeExceedsCube(place.getPlacedAgainstMaterial()) || place.getPlacedAgainstMaterial() == StateTypes.LECTERN ? 1.5 : 1;
double minAllowed = 1 - allowed;
double maxBound = isExtended ? 1.5 : 1.0;
double minBound = 1.0 - maxBound; // Usually 0.0
// ====================================================================================
// LOWER BOUND CHECK (< 0.0)
// ====================================================================================
// Why only MAX_DOUBLE_ERROR?
// Near 0.0, 'float' has extremely high resolution (down to E-45).
// It acts like a double. When the client has a tiny calculation error (e.g. -4.44E-16),
// the float cast preserves it exactly. We only need to account for the arithmetic noise.
if (cursor.getX() < minBound - MAX_DOUBLE_ERROR ||
cursor.getY() < minBound - MAX_DOUBLE_ERROR ||
cursor.getZ() < minBound - MAX_DOUBLE_ERROR) {
// Alert logic
String debug = String.format("cursor=%s limit=%.16f", cursor, minBound - MAX_DOUBLE_ERROR);
if (flagAndAlert(debug) && shouldModifyPackets() && shouldCancel()) {
place.resync();
}
return;
}
// ====================================================================================
// UPPER BOUND CHECK (> 1.0 or > 1.5)
// ====================================================================================
// Near 1.0, the "Float Step" is E-7. This is 16x larger than the Double error.
// If the calculation is slightly off, it might snap to the NEXT float.
// We tolerate exactly one "Float Step" of overflow.
double upperTolerance = FLOAT_STEP_AT_ONE;
// Note: Effectively Math.max(MAX_DOUBLE_ERROR, FLOAT_STEP_AT_ONE)
// but since FLOAT_STEP is always larger in MC coordinates, we just use it.
if (cursor.getX() > maxBound + upperTolerance ||
cursor.getY() > maxBound + upperTolerance ||
cursor.getZ() > maxBound + upperTolerance) {
// Alert logic
String debug = String.format("cursor=%s limit=%.16f", cursor, maxBound + upperTolerance);
if (flagAndAlert(debug) && shouldModifyPackets() && shouldCancel()) {
if (cursor.getX() < minAllowed || cursor.getY() < minAllowed || cursor.getZ() < minAllowed || cursor.getX() > allowed || cursor.getY() > allowed || cursor.getZ() > allowed) {
if (flagAndAlert() && shouldModifyPackets() && shouldCancel()) {
place.resync();
}
}
@@ -8,6 +8,7 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
@@ -19,7 +20,7 @@ public class FarPlace extends BlockPlaceCheck {
@Override
public void onBlockPlace(final BlockPlace place) {
if (!player.cameraEntity.isSelf() || player.inVehicle()) return;
if (player.gamemode == GameMode.SPECTATOR || player.inVehicle()) return;
Vector3i blockPos = place.position;
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3f;
import com.github.retrooper.packetevents.util.Vector3i;
@@ -53,7 +54,7 @@ public class MultiPlace extends BlockPlaceCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.cameraEntity.isSelf() || isTickPacket(event.getPacketType())) {
if (player.gamemode == GameMode.SPECTATOR || isTickPacket(event.getPacketType())) {
hasPlaced = false;
}
}
@@ -11,6 +11,7 @@ import ac.grim.grimac.utils.nmsutil.Ray;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3d;
@@ -33,7 +34,7 @@ public class RotationPlace extends BlockPlaceCheck {
@Override
public void onBlockPlace(final BlockPlace place) {
if (place.material == StateTypes.SCAFFOLDING) return;
if (!player.cameraEntity.isSelf())
if (player.gamemode == GameMode.SPECTATOR)
return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return;
if (flagBuffer > 0 && !didRayTraceHit(place)) {
@@ -49,7 +50,7 @@ public class RotationPlace extends BlockPlaceCheck {
@Override
public void onPostFlyingBlockPlace(BlockPlace place) {
if (place.material == StateTypes.SCAFFOLDING) return;
if (!player.cameraEntity.isSelf())
if (player.gamemode == GameMode.SPECTATOR)
return; // you don't send flying packets when spectating entities
if (player.inVehicle()) return;
@@ -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);
}
@@ -21,7 +21,7 @@ public class VehicleD extends Check implements PacketCheck {
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION && new WrapperPlayClientEntityAction(event).getAction() == WrapperPlayClientEntityAction.Action.START_JUMPING_WITH_HORSE) {
final EntityType vehicle = player.getVehicleType();
if (!EntityTypes.isTypeInstanceOf(vehicle, EntityTypes.ABSTRACT_HORSE) && !EntityTypes.isTypeInstanceOf(vehicle, EntityTypes.ABSTRACT_NAUTILUS)) {
if (!EntityTypes.isTypeInstanceOf(vehicle, EntityTypes.ABSTRACT_HORSE)) {
if (flagAndAlert("vehicle=" + (vehicle == null ? "null" : vehicle.getName().getKey().toLowerCase())) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
@@ -1,67 +0,0 @@
package ac.grim.grimac.checks.type;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCloseWindow;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCloseWindow;
import org.jetbrains.annotations.MustBeInvokedByOverriders;
public class InventoryCheck extends BlockPlaceCheck implements PacketCheck {
// Impossible transaction ID
protected static final long NONE = Long.MAX_VALUE;
protected long closeTransaction = NONE;
protected int closePacketsToSkip;
public InventoryCheck(GrimPlayer player) {
super(player);
}
@Override
@MustBeInvokedByOverriders
public void onPacketReceive(final PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLICK_WINDOW) {
// Disallow any clicks if inventory is closing
if (closeTransaction != NONE && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
player.inventory.needResend = true;
}
} else if (event.getPacketType() == PacketType.Play.Client.CLOSE_WINDOW) {
// Players with high ping can close inventory faster than send transaction back
if (closeTransaction != NONE && closePacketsToSkip-- <= 0) {
closeTransaction = NONE;
}
}
}
public void closeInventory() {
if (closeTransaction != NONE) {
return;
}
int windowId = player.inventory.openWindowID;
player.user.writePacket(new WrapperPlayServerCloseWindow(windowId));
// Force close inventory on server side
closePacketsToSkip = 1; // Sending close packet to itself, so skip it
PacketEvents.getAPI().getProtocolManager().receivePacket(
player.user.getChannel(), new WrapperPlayClientCloseWindow(windowId)
);
player.sendTransaction();
int transaction = player.lastTransactionSent.get();
closeTransaction = transaction;
player.latencyUtils.addRealTimeTask(transaction, () -> {
if (closeTransaction == transaction) {
closeTransaction = NONE;
}
});
player.user.flushPackets();
}
}
@@ -1,9 +1,8 @@
package ac.grim.grimac.command;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import org.incendo.cloud.CommandManager;
public interface BuildableCommand {
void register(CommandManager<Sender> manager, CloudCommandAdapter adapter);
void register(CommandManager<Sender> manager);
}
@@ -1,77 +0,0 @@
package ac.grim.grimac.command;
import ac.grim.grimac.command.commands.*;
import ac.grim.grimac.command.handler.GrimCommandFailureHandler;
import ac.grim.grimac.platform.api.command.CommandService;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import io.leangen.geantyref.TypeToken;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.exception.InvalidSyntaxException;
import org.incendo.cloud.key.CloudKey;
import org.incendo.cloud.processors.requirements.RequirementApplicable;
import org.incendo.cloud.processors.requirements.RequirementApplicable.RequirementApplicableFactory;
import org.incendo.cloud.processors.requirements.RequirementPostprocessor;
import org.incendo.cloud.processors.requirements.Requirements;
import java.util.function.Function;
import java.util.function.Supplier;
public class CloudCommandService implements CommandService {
public static final CloudKey<Requirements<Sender, SenderRequirement>> REQUIREMENT_KEY
= CloudKey.of("requirements", new TypeToken<>() {});
public static final RequirementApplicableFactory<Sender, SenderRequirement> REQUIREMENT_FACTORY
= RequirementApplicable.factory(REQUIREMENT_KEY);
private boolean commandsRegistered = false;
private final Supplier<CommandManager<Sender>> commandManagerSupplier;
private final CloudCommandAdapter commandAdapter;
public CloudCommandService(Supplier<CommandManager<Sender>> commandManagerSupplier, CloudCommandAdapter commandAdapter) {
this.commandManagerSupplier = commandManagerSupplier;
this.commandAdapter = commandAdapter;
}
public void registerCommands() {
if (commandsRegistered) return;
CommandManager<Sender> commandManager = commandManagerSupplier.get();
new GrimPerf().register(commandManager, commandAdapter);
new GrimDebug().register(commandManager, commandAdapter);
new GrimAlerts().register(commandManager, commandAdapter);
new GrimProfile().register(commandManager, commandAdapter);
new GrimSendAlert().register(commandManager, commandAdapter);
new GrimHelp().register(commandManager, commandAdapter);
new GrimHistory().register(commandManager, commandAdapter);
new GrimReload().register(commandManager, commandAdapter);
new GrimSpectate().register(commandManager, commandAdapter);
new GrimStopSpectating().register(commandManager, commandAdapter);
new GrimLog().register(commandManager, commandAdapter);
new GrimVerbose().register(commandManager, commandAdapter);
new GrimVersion().register(commandManager, commandAdapter);
new GrimDump().register(commandManager, commandAdapter);
new GrimBrands().register(commandManager, commandAdapter);
new GrimList().register(commandManager, commandAdapter);
new GrimTestWebhook().register(commandManager, commandAdapter);
final RequirementPostprocessor<Sender, SenderRequirement>
senderRequirementPostprocessor = RequirementPostprocessor.of(
REQUIREMENT_KEY,
new GrimCommandFailureHandler()
);
commandManager.registerCommandPostProcessor(senderRequirementPostprocessor);
registerExceptionHandler(commandManager, InvalidSyntaxException.class, e -> MessageUtil.miniMessage(e.correctSyntax()));
commandsRegistered = true;
}
protected <E extends Exception> void registerExceptionHandler(CommandManager<Sender> commandManager, Class<E> ex, Function<E, ComponentLike> toComponent) {
commandManager.exceptionController().registerHandler(ex,
(c) -> c.context().sender().sendMessage(toComponent.apply(c.exception()).asComponent().colorIfAbsent(NamedTextColor.RED))
);
}
}
@@ -2,7 +2,6 @@ package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
@@ -13,7 +12,7 @@ import java.util.Objects;
public class GrimAlerts implements BuildableCommand {
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("alerts", Description.of("Toggle alerts for the sender"))
@@ -2,7 +2,6 @@ package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.context.CommandContext;
@@ -13,7 +12,7 @@ import java.util.Objects;
public class GrimBrands implements BuildableCommand {
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("brands", Description.of("Toggle brands for the sender"))
@@ -1,10 +1,8 @@
package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.checks.debug.HitboxDebugHandler;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.command.PlayerSelector;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.MessageUtil;
@@ -21,33 +19,26 @@ import org.jetbrains.annotations.Nullable;
public class GrimDebug implements BuildableCommand {
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
Command.Builder<Sender> grimCommand = commandManager.commandBuilder("grim", "grimac");
// Register "debug" subcommand
Command.Builder<Sender> debugCommand = grimCommand
.literal("debug", Description.of("Toggle debug output for a player"))
.permission("grim.debug")
.optional("target", adapter.singlePlayerSelectorParser())
.optional("target", GrimAPI.INSTANCE.getCommandAdapter().singlePlayerSelectorParser())
.handler(this::handleDebug);
// Register "consoledebug" subcommand
Command.Builder<Sender> consoleDebugCommand = grimCommand
.literal("consoledebug", Description.of("Toggle console debug output for a player"))
.permission("grim.consoledebug")
.required("target", adapter.singlePlayerSelectorParser())
.required("target", GrimAPI.INSTANCE.getCommandAdapter().singlePlayerSelectorParser())
.handler(this::handleConsoleDebug);
Command.Builder<Sender> hitboxDebugCommand = grimCommand
.literal("hitboxdebug", Description.of("Toggle hitbox debug visualization"))
.permission("grim.hitboxdebug")
.optional("target", adapter.singlePlayerSelectorParser(), Description.of("Player to debug (defaults to self if sender is player)"))
.handler(this::handleHitboxDebug);
// Register command
commandManager.command(debugCommand);
commandManager.command(consoleDebugCommand);
commandManager.command(hitboxDebugCommand);
}
private void handleDebug(@NotNull CommandContext<Sender> context) {
@@ -97,48 +88,6 @@ public class GrimDebug implements BuildableCommand {
sender.sendMessage(message);
}
private void handleHitboxDebug(@NotNull CommandContext<Sender> context) {
Sender sender = context.sender();
PlayerSelector playerSelector = context.getOrDefault("target", null);
// Hitbox debug requires a *player* to be the listener (the one seeing the boxes)
if (!sender.isPlayer()) {
sender.sendMessage(MessageUtil.getParsedComponent(sender,
"hitboxdebug-player-only",
"%prefix% &cHitbox debug can only be toggled by players.")
);
return;
}
// Determine the target player whose hitboxes are being debugged
GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer());
if (targetGrimPlayer == null) return;
// Get the sender's GrimPlayer data, as they are the listener
GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId());
if (senderGrimPlayer == null) {
sender.sendMessage(Component.text("Could not find your player data to register as a listener.", NamedTextColor.RED));
return;
}
// Get the HitboxDebugHandler check instance and toggle the listener
HitboxDebugHandler hitboxHandler = targetGrimPlayer.checkManager.getCheck(HitboxDebugHandler.class);
if (hitboxHandler == null) {
sender.sendMessage(Component.text("HitboxDebugHandler check not found for target player.", NamedTextColor.RED));
return;
}
boolean enabled = hitboxHandler.toggleListener(senderGrimPlayer); // Pass the sender/listener
// Send feedback message
Component message = Component.text()
.append(Component.text("Hitbox debug listener for ", NamedTextColor.GRAY))
.append(Component.text(targetGrimPlayer.getName(), NamedTextColor.WHITE))
.append(Component.text(enabled ? " enabled." : " disabled.", NamedTextColor.GRAY))
.build();
sender.sendMessage(message);
}
private @Nullable GrimPlayer parseTarget(@NotNull Sender sender, @Nullable Sender t) {
if (sender.isConsole() && t == null) {
sender.sendMessage(MessageUtil.getParsedComponent(sender, "console-specify-target", "%prefix% &cYou must specify a target as the console!"));
@@ -3,7 +3,6 @@ package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.PlatformPlugin;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.common.PropertiesUtil;
@@ -30,7 +29,7 @@ public class GrimDump implements BuildableCommand {
private String link = null; // these links should not expire for a while
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("dump", Description.of("Generate a debug dump"))
@@ -2,7 +2,6 @@ package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import org.incendo.cloud.CommandManager;
@@ -12,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
public class GrimHelp implements BuildableCommand {
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("help", Description.of("Display help information"))
@@ -4,7 +4,6 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.manager.violationdatabase.Violation;
import ac.grim.grimac.manager.violationdatabase.ViolationDatabaseManager;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.MessageUtil;
@@ -19,12 +18,12 @@ import java.util.concurrent.TimeUnit;
public class GrimHistory implements BuildableCommand {
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(
commandManager.commandBuilder("grim", "grimac")
.literal("history", "hist")
.permission("grim.help")
.required("target", StringParser.stringParser(), adapter.onlinePlayerSuggestions())
.required("target", StringParser.stringParser(), GrimAPI.INSTANCE.getCommandAdapter().onlinePlayerSuggestions())
.optional("page", IntegerParser.integerParser())
.permission("grim.history")
.handler(this::handleHistory)
@@ -4,7 +4,6 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.api.GrimIdentity;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.command.CommandUtils;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.player.GrimPlayer;
@@ -29,7 +28,7 @@ public class GrimList implements BuildableCommand {
// Mainly for debugging purposes. Useful for seeing which players are exempt or not.
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
commandManager.command(commandManager.commandBuilder("grim", "grimac")
.literal("list")
.permission("grim.list")
@@ -3,7 +3,6 @@ package ac.grim.grimac.command.commands;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.command.BuildableCommand;
import ac.grim.grimac.manager.init.start.SuperDebug;
import ac.grim.grimac.platform.api.manager.cloud.CloudCommandAdapter;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.anticheat.MessageUtil;
@@ -45,7 +44,7 @@ public class GrimLog implements BuildableCommand {
try {
urlConn.setDoOutput(true);
urlConn.setRequestMethod("POST");
urlConn.addRequestProperty("User-Agent", "LightningGrim/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion());
urlConn.addRequestProperty("User-Agent", "GrimAC/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion());
urlConn.addRequestProperty("Content-Type", type); // Not really yaml, but looks nicer than plaintext
urlConn.setRequestProperty("Content-Length", Integer.toString(log.length()));
try (OutputStream stream = urlConn.getOutputStream()) {
@@ -69,7 +68,7 @@ public class GrimLog implements BuildableCommand {
}
@Override
public void register(CommandManager<Sender> commandManager, CloudCommandAdapter adapter) {
public void register(CommandManager<Sender> commandManager) {
Command<Sender> command = commandManager.commandBuilder("grim", "grimac")
.literal("log", "logs")
.permission("grim.log")
Loaded 100 of 386 files, more files were not shown because too many files have changed in this diff. Show more