Compare commits

..
Author SHA1 Message Date
Axionize cf115fd9c5 Disable requirements processor 2025-03-25 03:46:17 -04:00
Axionize 999f083363 I hate this 2025-03-25 03:24:34 -04:00
Axionize 96657b399f Fix build issues with downloading Conditional Mixin library for PE Fabric 2025-03-25 03:24:18 -04:00
Axionize 2cdca3ed72 Bump RAM allocation to Gradle in root gradle.properties 2025-03-24 14:35:25 -04:00
Axionize 196d4f592b Minor Build optimization 2025-03-24 08:48:19 -04:00
Axionize 588ba8def2 New API refactor with getEventBus() 2025-03-24 01:19:45 -04:00
Axionize 55cf328063 Only include fabricAPI modules we use, reuse dependency code for multiple projects 2025-03-23 08:10:26 -04:00
Axionize ddab7562fd Add support for Spigot 1.17 - 1.18.2 itemUsageReset()
- Also decrease allocated via gradle.properties
- Forces rebuild with new PE which will fix Geyser detection
2025-03-23 07:51:12 -04:00
Axionize 804e34f20d Make Grim rely on JiJed and loaded PE API; Transition PacketPlayerDigging to be cross-platform from upstream 2025-03-23 03:38:38 -04:00
Axionize fa65721116 Translation parity (and more/better) with pre-platform-independence port 2025-03-23 01:50:51 -04:00
Axionize 0c81517639 Fix updating native ServerPlayerEntity reference; Refactor @NonNull annotations 2025-03-23 01:50:50 -04:00
Axionize b71c44c2e1 Initable refactor; support multiple types of initables as one object; shutdown fabric bstats 2025-03-23 01:49:33 -04:00
Axionize d7437bce97 Fix Geyser-Fabric compatability 2025-03-23 01:49:33 -04:00
Axionize 888f209994 Build against slim configuralize for fabric support 2025-03-23 01:49:33 -04:00
Axionize f6397c978b Bump snakeyaml version for Fabric support 2025-03-23 01:49:33 -04:00
Axionize d612a522cc Fix library relocation typo 2025-03-23 01:49:33 -04:00
Axionize 661010805f Fix build issues with relocation on bukkit; force rebuild for fabric registry fix with PE 2025-03-23 01:49:33 -04:00
Axionize 6848c03e47 Code cleanup to address code review concerns 2025-03-23 01:49:33 -04:00
Axionize 0b168e2d60 Fix Build error from rebasing https://github.com/GrimAnticheat/Grim/commit/aab710dd8b55bc6fbe88f0f4c79aa214527a20dd 2025-03-23 01:49:33 -04:00
Axionize a1d0a6a73d Fix rare preconditions error with checking for null platformPlayer 2025-03-23 01:49:33 -04:00
Axionize a88c506518 Cleanup Fabric buildscript 2025-03-23 01:49:33 -04:00
Axionize 04f4bd7d21 Hopefully Fix inventory errors 2025-03-23 01:49:33 -04:00
Axionize cae28699a3 Fix running fabric in IDE and dev mode
Credit: @Bawnorton
2025-03-23 01:49:33 -04:00
Axionize ee689d1503 Fix 1.8 support using non-existent inventory method 2025-03-23 01:49:33 -04:00
Axionize 695e1ad5a3 Bump Fabric loom version; trigger rebuild on updated PE fork with Fabric fixes 2025-03-23 01:49:33 -04:00
Axionize bc766b51dd Minor code cleanup 2025-03-23 01:49:33 -04:00
Axionize fb1f3cda72 Code cleanup 2025-03-23 01:49:33 -04:00
Axionize 554d6d62db Configure builds to use shared lib versions files; push for rebuild with updated PE; (Working 1.20.1) 2025-03-23 01:49:33 -04:00
Axionize b9f71bce4e Fix project build issues and fix 1.20.1 support 2025-03-23 01:49:31 -04:00
Axionize f4f9092bd7 Fix Legacy (< 1.16.5) Big DripLeaf CollisionBox modeling 2025-03-23 01:49:19 -04:00
Axionize ff461cf041 Actuallly commit 1.20.4 support 2025-03-23 01:49:18 -04:00
Axionize 9b5264f0f3 Add 1.20.1 support (once PE is fixed) 2025-03-23 01:49:18 -04:00
Axionize 5cb5ce634d Refactor Fabric to support 1.21.4, 1.21.1, and (soon) 1.16.1 2025-03-23 01:49:18 -04:00
Axionize 0403565830 Fix endTick on Folia 2025-03-23 01:49:18 -04:00
Axionize 4d0a3696c9 PE datatype refactor; Fix Folia support 2025-03-23 01:49:18 -04:00
Axionize f1440bad93 Moderate refactoring and codebase cleanup
Fixed GitHub actions

Note that Fabric platform is missing:
- Pistons
- Providing an instance of GrimExternalAPI for other plugins/mods to get
- and more
2025-03-23 01:49:17 -04:00
Axionize a92e1e3892 Fix scheduler hierachy issue 2025-03-23 01:44:38 -04:00
Axionize c60355536e Temporarily build against my fork to fix PacketEvent's broken Fabric support 2025-03-23 01:44:38 -04:00
Axionize 2aeafe14cc Build against new Grim API instead of locally 2025-03-23 01:44:38 -04:00
Axionize e5d011b681 Fix Actions workflow 2025-03-23 01:43:34 -04:00
Axionize 5b59392817 Finish Fabric buildscript; now builds runnable jar 2025-03-23 01:43:34 -04:00
Axionize 656e73a07d Fix Fabric execution and shutdown; Initial Fabric build script 2025-03-23 01:43:34 -04:00
Axionize 7d9836ca50 Beta Fabric Support Complete + General Bug Fixes:
- Generate autocompletions for permissions
- Make OP, and enable-on-join permissions false by default even for ops

- Fixed usage of isWorldLoaded() in pre-migration codebase from breaking 1.8 support
- Numerous command loader bugs fixed
- A lot more
2025-03-23 01:43:34 -04:00
Axionize 7e91c739b2 Refactor resyncPosition() internally; may move to platformPlayer in future 2025-03-23 01:43:34 -04:00
Axionize 9aaf8b9491 Platform loading fixes 2025-03-23 01:43:34 -04:00
Axionize 3869a2077d Fabric Support MVP
- Registering permissions likely incomplete
- Building jar incomplete; only works in Debug mode
- Exempting on reload not done
- Plugin Channels not implemented
2025-03-23 01:43:34 -04:00
Axionize 525c380560 Complete multi-module project setup 2025-03-23 01:43:34 -04:00
Axionize ca7326cbd7 Multi-module project init setup 2025-03-23 01:43:32 -04:00
Axionize e9f2d29fd5 Platform Independence Achieved! 2025-03-23 01:43:26 -04:00
Axionize dfdab62763 Refactor Part 3:
Checkpoint
Need to implement:
- Fix PlaceHolderAPI and ManInMyVan's MessageUtil logic error
- Add BStats back
- Add platform independent Permissions
- Make TickEndEvent handling platform independent

Need to Test:
- ProxyAlertMessenger
- Commands
2025-03-23 01:43:26 -04:00
Axionize e339626ce1 Fix plugin compilation and exeution of commands 2025-03-23 01:43:26 -04:00
Axionize b0df122cb9 Almost there; just events and bukkit extensions left 2025-03-23 01:43:26 -04:00
Axionize ae284e2886 Stash code 2025-03-23 01:43:26 -04:00
Axionize 586f4bde69 Refactor Part 2:
- Replace Vectors
- Replace ChatColor.translateColorCodes()
2025-03-23 01:43:25 -04:00
Axionize c3d1b14dc1 Seperated/Abstracted:
- Thread Management
- GrimEntity/PlatformPlayer + PlatformInventory
- PlatformWorld, PlatformChunk, Location
- Removed many bukkit dependencies
- Migrated Bukkit Events to own code section

- Disabled BStats temporarily
2025-03-23 01:43:24 -04:00
627 changed files with 10748 additions and 20930 deletions

No files matched your search

-5
View File
@@ -7,8 +7,6 @@ indent_style = space
insert_final_newline = true
max_line_length = 100
tab_width = 4
end_of_line = crlf
trim_trailing_whitespace = true
# intellij specific configuration
ij_continuation_indent_size = 8
@@ -17,6 +15,3 @@ ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_wrap_on_typing = false
[.github/ISSUE_TEMPLATE/*.yml]
indent_size = 2
-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
+7 -15
View File
@@ -5,7 +5,7 @@ body:
- type: textarea
attributes:
label: Describe the bypass and how to replicate it
description: Provide a video or detailed instructions to help replicate it.
description: Provide a video, or detailed instructions to help replicate it.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -17,8 +17,8 @@ body:
attributes:
label: Grim version
description: |
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
validations:
required: true
@@ -26,23 +26,15 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: input
attributes:
label: Client version
description: |
Provide the version of your Minecraft client. Also provide the protocol version if changed.
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
validations:
required: true
- type: textarea
attributes:
label: Plugins/Mods
label: Plugins
description: |
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
List all plugins running on your server. To list plugins, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
+8 -20
View File
@@ -6,7 +6,7 @@ body:
attributes:
label: Stack trace
description: |
Provide the entire stack trace with no attachments. If you would like, you can use a text pasting site such as https://mclo.gs.
Please include the entire stack trace with no attachments. If you would like, you can use a text pasting site such as https://paste.gg.
placeholder: Paste the stack trace or link to the stack trace here
validations:
required: true
@@ -14,7 +14,7 @@ body:
- type: textarea
attributes:
label: How to replicate it if known
description: A video or detailed instructions to help replicate the issue would help.
description: A video, or detailed instructions to help replicate the issue would help.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -26,8 +26,8 @@ body:
attributes:
label: Grim version
description: |
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
validations:
required: true
@@ -35,27 +35,15 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: dropdown
id: type
attributes:
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
validations:
required: true
- type: textarea
attributes:
label: Plugins/Mods
label: Plugins
description: |
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
List all plugins running on your server. To list plugins, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
+8 -8
View File
@@ -20,8 +20,8 @@ body:
attributes:
label: Grim version
description: |
Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
validations:
required: true
@@ -29,7 +29,7 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
validations:
required: true
@@ -37,16 +37,16 @@ body:
attributes:
label: Client version
description: |
Provide the version of your Minecraft client. You must be able to reproduce the false positive using a vanilla client.
Using Lunar Client, TLauncher, Fabric, etc, will result in your issue being closed.
Provide the version of your Minecraft client. You must be able to reproduce the false positive using the vanilla client.
Lunar Client, TLauncher etc. will result in your issue being closed.
validations:
required: true
- type: textarea
attributes:
label: Plugins/Mods
label: Plugins
description: |
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
List all plugins running on your server. To list plugins, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
+2 -2
View File
@@ -5,6 +5,6 @@ body:
- type: textarea
attributes:
label: Feature description
description: Describe the feature and why it should be added.
description: Describe the feature and why it should be added
validations:
required: true
required: true
+10 -21
View File
@@ -1,11 +1,11 @@
name: Plugin/mod incompatibility or other bug
description: Report a bug or plugin/mod incompatibility here
name: Plugin incompatibility or other bug
description: Report a bug or plugin incompatibility here
labels: ["bug"]
body:
- type: textarea
attributes:
label: Describe the bug and how to replicate it
description: A video or detailed instructions to help replicate the issue would help.
description: A video, or detailed instructions to help replicate the issue would help.
placeholder: |
Step 1. Do this
Step 2. Then do this
@@ -16,8 +16,9 @@ body:
- type: textarea
attributes:
label: Grim version
description: Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
description: |
Provide the git commit or the version in the jar's name.
placeholder: A example would be b85c2d0 or grim version 2.3.5, etc
validations:
required: true
@@ -25,27 +26,15 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
validations:
required: true
- type: dropdown
id: type
attributes:
label: Affected Platform(s)
description: Which platforms does this issue affect? If you aren't sure, select both.
options:
- "Bukkit"
- "Fabric"
multiple: true
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
validations:
required: true
- type: textarea
attributes:
label: Plugins/Mods
label: Plugins
description: |
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
List all plugins running on your server. To list plugins, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
@@ -19,15 +19,8 @@ body:
- type: input
attributes:
label: Grim Log
description: Provide the generated /grim log link.
validations:
required: true
- type: textarea
attributes:
label: Grim version
description: Provide the version of Grim running on the server. You can run `/grim version` to check.
placeholder: Examples include 2.3.73-8ddd7f1, 2.3.72, etc.
description: |
Provide the generated /grim log link.
validations:
required: true
@@ -35,7 +28,7 @@ body:
attributes:
label: Server version
description: |
Run `/version` on your server to check. Provide both the server type (ie Paper, Airplane, etc) & the Minecraft version.
Run `/version` on your server to check. Provide both the server type (paper, airplane, etc) & the minecraft version.
validations:
required: true
@@ -43,16 +36,16 @@ body:
attributes:
label: Client version
description: |
Provide the version of your Minecraft client. You must be able to reproduce the false positive using a vanilla client.
Using Lunar Client, TLauncher, Fabric, etc, will result in your issue being closed.
Provide the version of your Minecraft client. You must be able to reproduce the false positive using the vanilla client.
Lunar Client, TLauncher etc. will result in your issue being closed.
validations:
required: true
- type: textarea
attributes:
label: Plugins/Mods
label: Plugins
description: |
List all plugins or mods installed on your server. To list plugins on Bukkit-based servers, run `/plugins`.
List all plugins running on your server. To list plugins, run `/plugins`.
If your server is behind a proxy such as Bungeecord, you should list the proxy's plugins as well.
validations:
required: true
required: true
-253
View File
@@ -1,253 +0,0 @@
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
type: boolean
release-version-type:
description: 'The version type for Modrinth (e.g., release, beta, alpha).'
required: true
type: string
release-changelog:
description: 'The changelog content for the release.'
required: false
type: string
default: ''
run-build-scan:
description: 'Whether to enable Develocity build scan'
required: true
type: boolean
publish-artifacts:
description: 'If true, uploads artifacts to Modrinth and other distribution sites.'
required: true
type: boolean
secrets:
MODRINTH_ID:
required: false
MODRINTH_TOKEN:
required: false
BUILD_UPDATE_TOKEN:
required: true
BUILD_UPDATE_URL:
required: true
jobs:
build:
runs-on: ${{ inputs.runner }}
permissions:
# Grant permission to write contents (for creating tags/refs)
contents: write
packages: write
env:
MODRINTH_ID: ${{ secrets.MODRINTH_ID }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
GRIM_MAIN_BRANCH: ${{ github.event.repository.default_branch }}
BUILD_UPDATE_TOKEN: ${{ secrets.BUILD_UPDATE_TOKEN }}
steps:
- name: Checkout repository with full history
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 200
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref_name != github.event.repository.default_branch && github.ref_name != 'dev' }}
- name: Cache Gradle Loom Cache
uses: actions/cache@v4
with:
path: .gradle/loom-cache
key: loom-cache-${{ runner.os }}-${{ hashFiles('**/libs.versions.toml', 'fabric/**/build.gradle.kts') }}
restore-keys: |
loom-cache-${{ runner.os }}
- name: Set Git Environment Variables
run: |
echo "GRIM_GIT_REPO=${{ github.event.repository.name }}" >> $GITHUB_ENV
echo "GRIM_GIT_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV
- name: Compute version and Build with Gradle
id: build_with_version
run: |
# If any command in a pipeline fails, treat the pipeline as failed.
set -o pipefail
BUILD_ARGS="build"
if [[ "${{ inputs.is-release-build }}" == "true" ]]; then
BUILD_ARGS="$BUILD_ARGS -Prelease=true"
fi
# Conditionally add the --scan flag
if [[ "${{ inputs.run-build-scan }}" == "true" ]]; then
BUILD_ARGS="$BUILD_ARGS --scan"
fi
echo "Running: ./gradlew printVersion $BUILD_ARGS"
# Use 'tee' to both display the output live and save it to a file for parsing
VERSION=$(./gradlew printVersion $BUILD_ARGS | tee build-output.log | grep '^VERSION=' | tail -n1 | cut -d'=' -f2)
echo "build_version=$VERSION" >> $GITHUB_OUTPUT
echo "BUILD_VERSION=$VERSION" >> $GITHUB_ENV
echo "Build version: $VERSION"
- name: Publish Build Scan URL
# Only run this step if a scan was requested
if: ${{ inputs.run-build-scan == true }}
run: |
# Search the log file for the build scan URL and extract it
SCAN_URL=$(grep -o 'https://gradle.com/s/[^ ]*' build-output.log | head -n 1 || true)
if [[ -n "$SCAN_URL" ]]; then
echo "Found build scan URL: $SCAN_URL"
# Post a highly visible link to the Job Summary page
echo "### Gradle Build Scan" >> $GITHUB_STEP_SUMMARY
echo "A public build scan has been published and is available for inspection." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "[**View Build Scan →**]($SCAN_URL)" >> $GITHUB_STEP_SUMMARY
else
echo "Could not find build scan URL in build-output.log" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v6
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
with:
name: grimac-fabric
path: ${{ github.workspace }}/fabric/build/libs/grimac-fabric-${{ env.BUILD_VERSION }}.jar
if-no-files-found: error
#------------------------------------------------------------------
# Generate/Prepare Changelog
#------------------------------------------------------------------
- name: Generate Changelog with git-cliff (for Dev Builds)
if: ${{ !inputs.is-release-build }}
id: git-cliff
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --verbose ${{ github.event.before }}..${{ github.event.after }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # git-cliff needs a token
- name: Prepare Final Changelog
id: changelog
# Add this 'env' block to safely load the changelogs as environment variables
env:
RELEASE_NOTES: ${{ inputs.release-changelog }}
DEV_NOTES: ${{ steps.git-cliff.outputs.content }}
run: |
NOTES=""
if [[ "${{ inputs.is-release-build }}" == "true" ]]; then
# Use the environment variable for the release changelog.
# The double quotes are critical to preserve newlines!
NOTES="$RELEASE_NOTES"
else
# Use the environment variable for the dev changelog.
NOTES="$DEV_NOTES"
fi
if [ -z "$NOTES" ]; then
NOTES="* No new code changes in this build."
fi
# This is the correct way to set a multi-line output variable in Actions
{
echo "notes<<EOF"
echo "$NOTES"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Generate build number
if: contains(fromJSON('["merge","release","main","lightning"]'), github.ref_name) && inputs.publish-artifacts == true && env.MODRINTH_ID != '' && env.MODRINTH_TOKEN != ''
id: buildnumber
uses: onyxmueller/build-tag-number@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
#------------------------------------------------------------------
# Publish to Modrinth & API
#------------------------------------------------------------------
- name: Publish Bukkit 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: 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 }}
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'
- name: Publish to API
if: ${{ inputs.publish-artifacts == true && env.BUILD_UPDATE_TOKEN != '' && inputs.is-release-build == false }}
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 }}"
+47 -51
View File
@@ -12,64 +12,60 @@
name: "CodeQL"
on:
workflow_dispatch:
push:
branches: [dev]
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '30 15 * * 2'
jobs:
analyze:
name: Analyze
runs-on: tenki-standard-autoscale
permissions:
actions: read
contents: read
security-events: write
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java-kotlin']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# 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@v4
# 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
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
+67 -46
View File
@@ -1,49 +1,70 @@
name: Gradle Package (Development)
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
name: Gradle Package
on:
workflow_dispatch:
inputs:
run-build-scan:
description: 'Run with public build scan (adds --scan)'
required: true
type: boolean
default: false
publish-artifacts:
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'
- '2.0'
- 'dev'
- 'release'
- 'release/**'
- 'lightning'
paths:
- 'bukkit/**'
- 'common/**'
- 'fabric/**'
- '**.java'
- '**.kt'
- '**.kts'
- '**.toml'
[push, workflow_dispatch]
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' }}
publish-artifacts: ${{ github.event.inputs.publish-artifacts != 'false' }}
secrets:
MODRINTH_ID: ${{ secrets.MODRINTH_ID }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
BUILD_UPDATE_TOKEN: ${{ secrets.BUILD_UPDATE_TOKEN }}
BUILD_UPDATE_URL: ${{ secrets.BUILD_UPDATE_URL }}
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
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
# Temporary tasks so built jar works because PacketEvents won't merge my fixes for their broken fabric support
- name: Clone Axionize's PacketEvents Repository
run: |
git clone -b fix/fabric-events https://github.com/Axionize/packetevents.git packetevents
cd packetevents
echo "Cloned PacketEvents repository"
- name: Build and Publish PacketEvents to Maven Local
uses: gradle/actions/setup-gradle@v3
with:
build-root-directory: packetevents
arguments: publishToMavenLocal
- name: Extract Version from build.gradle.kts
id: extract-version
run: |
# Extract the version from build.gradle.kts using grep and sed
VERSION=$(grep "^version\s*=" build.gradle.kts | sed 's/version\s*=\s*"\(.*\)"/\1/')
echo "VERSION: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
arguments: build
- name: Upload Bukkit Artifact
uses: actions/upload-artifact@v4
with:
name: grimac-bukkit
path: ${{ github.workspace }}/bukkit/build/libs/grimac-bukkit-${{ env.VERSION }}.jar
- name: Upload Fabric Artifact
uses: actions/upload-artifact@v4
with:
name: grimac-fabric
path: ${{ github.workspace }}/fabric/build/libs/grimac-fabric-${{ env.VERSION }}.jar
-22
View File
@@ -1,22 +0,0 @@
name: Publish Release
on:
release:
types: [published]
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
release-changelog: ${{ github.event.release.body }}
run-build-scan: false
publish-artifacts: true
secrets:
MODRINTH_ID: ${{ secrets.MODRINTH_ID }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
BUILD_UPDATE_TOKEN: ${{ secrets.BUILD_UPDATE_TOKEN }}
BUILD_UPDATE_URL: ${{ secrets.BUILD_UPDATE_URL }}
-3
View File
@@ -19,7 +19,6 @@ build/
.gradletasknamecache
gradle-app.setting
!gradle-wrapper.jar
buildSrc/.kotlin
##############################
## IntelliJ
@@ -73,5 +72,3 @@ nb-configuration.xml
##############################
run/
fabric/run/
.ignored/
-40
View File
@@ -1,40 +0,0 @@
# Contributing to LightningGrim
Thank you for your interest in contributing to LightningGrim. 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.*
### Pull Request Guidelines
- **Compatibility**
- Any changes must be compatible with
the [supported environments](https://github.com/GrimAnticheat/Grim/wiki/Supported-environments) (Spigot, Paper, Folia, Fabric, etc.)
- The plugin must be compatible with minecraft versions 1.8 and higher. Exempting checks from specific versions is acceptable.
- The plugin must be able to run on Java 17 or higher. Changes that don't support Java 17 at runtime will not be accepted.
- **Non-acceptable pull requests**
- Heuristic-based checks will not be accepted; however, basic rate limiting such as blocking attacks based on CPS is acceptable.
- Checks that can be easily circumvented that don't block packets or fix anything are likely to not be accepted.
- Checks or features that are too specific to a single environment or minecraft version are likely to not be accepted.
- Changes that require large or unnecessary dependencies will likely not be accepted.
- **Pull request formatting**
- Create a new branch for your feature or fix when forking the repository.
- Reference related issues in your pull request description if applicable.
- Write clear and descriptive commit messages.
- **Code styling**
- Add code comments for complex logic or significant changes.
- Try to keep your code clean and avoid duplication.
- Thoroughly test your changes before submitting your pull request.
### Development Notes
- LightningGrim 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
- Join our [Discord](https://discord.grim.ac) if you have questions or need assistance.
- Refer to the [Wiki](https://github.com/GrimAnticheat/Grim/wiki) for project documentation.
-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
+31 -67
View File
@@ -1,75 +1,42 @@
<div align="center">
<h1>LightningGrim</h1>
# GrimAC
<div>
<a href="https://github.com/GrimAnticheat/Grim/actions/workflows/gradle-publish.yml">
<img alt="Workflow" src="https://img.shields.io/github/actions/workflow/status/GrimAnticheat/Grim/gradle-publish.yml?style=flat&logo=github"/>
</a>&nbsp;&nbsp;
<a href="https://modrinth.com/plugin/grimac">
<img alt="Modrinth" src="https://img.shields.io/modrinth/v/LJNGWSvH?style=flat&label=version&logo=modrinth">
</a>&nbsp;&nbsp;
<a href="https://modrinth.com/plugin/grimac#download">
<img alt="Downloads" src="https://img.shields.io/modrinth/dt/LJNGWSvH?style=flat&logo=modrinth&label=downloads&link=https%3A%2F%2Fmodrinth.com%2Fplugin%2Fgrimac%23download">
</a>&nbsp;&nbsp;
<a href="https://discord.grim.ac">
<img alt="Discord" src="https://img.shields.io/discord/811396969670901800?style=flat&label=discord&logo=discord">
</a>
</div>
<br>
</div>
GrimAC is an open source Minecraft anticheat designed for 1.21 and supports 1.8-1.21. It is free while in beta. It will eventually become paid and/or will include offering additional subscription based paid checks. Geyser players are fully exempt.
LightningGrim is an open source Minecraft anticheat fork 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.
This project is considered feature complete for the 2.0 (open-source) branch of this project. If you would like a bugfix or enhancement and cannot sponsor the work, pull requests are welcome.
## Changes over GrimAC:
## Downloads
- [Modrinth](https://modrinth.com/plugin/grimac)
- [Hangar](https://hangar.papermc.io/GrimAnticheat/GrimAnticheat)
- [SpigotMC](https://www.spigotmc.org/resources/grim-anticheat.99923/)
- *For bleeding edge builds use* [Github artifacts](https://nightly.link/GrimAnticheat/Grim/workflows/gradle-publish/2.0/artifact.zip)
- 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...)
## Installation notes
> [!WARNING]
> Java 17 is now required. More information [here](https://github.com/GrimAnticheat/Grim/wiki/Updating-to-Java-17).
- Paper, Spigot, and Folia are currently supported.
- If you use Geyser, place Floodgate on the backend server so grim can exempt bedrock players. Grim cannot access the Floodgate API if it is on the proxy.
- If you use ViaVersion, it should be on the backend server as movement is highly dependent on client version.
## Requirements & Installation
## Support & wiki information
- Support & discussion: [Discord](https://discord.com/invite/kqQAhTmkUF)
- Report issues: [Issues](https://github.com/GrimAnticheat/Grim/issues/new/choose)
- Wiki & examples: [Wiki](https://github.com/GrimAnticheat/Grim/wiki)
- Java 17 or higher. *For more details, see [Updating-to-Java-17](https://github.com/GrimAnticheat/Grim/wiki/Updating-to-Java-17).*
- A Spigot, Paper, Folia, or Fabric server environment. *For more details, see [Supported-environments](https://github.com/GrimAnticheat/Grim/wiki/Supported-environments).*
## Developer API
Grim's API allows you to integrate Grim into your own plugins.
- API repository: [GrimAPI](https://github.com/GrimAnticheat/GrimAPI)
- Wiki info: [Wiki](https://github.com/GrimAnticheat/GrimAPI)
If you use a proxy such as Velocity or Bungeecord:
- If you use Geyser, Floodgate must be installed on the backend server (where Grim is) so Grim can access the Floodgate API.
- If you use ViaVersion, it must be installed on the backend server (where Grim is) ONLY.
Grim does not support having ViaVersion installed on the proxy, even if it is also installed on the backend.
## How to compile
## Resources
1. `git clone https://github.com/GrimAnticheat/Grim.git`
2. `cd Grim`
3. `./gradlew build`
4. The final jar will compile into the build/libs folder
- For documentation and examples visit the [Wiki](https://github.com/GrimAnticheat/Grim/wiki).
- For answers to commonly asked questions visit the [FAQ](https://github.com/GrimAnticheat/Grim/wiki/FAQ).
- For community support and project discussion join our [Discord](https://discord.grim.ac).
## Grim supremacy
## Pull Requests
See [Contributing](CONTRIBUTING.md) for more information about contributing and what our guidelines
are.
## Developer Plugin API
LightningGrim's plugin API allows you to integrate LightningGrim 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`
4. The final jars will compile into the `<platform>/build/libs` folders
## LightningGrim Supremacy
What makes LightningGrim stand out against other anticheats?
What makes Grim stand out against other anticheats?
### Movement Simulation Engine
@@ -77,9 +44,7 @@ What makes LightningGrim stand out against other anticheats?
* This covers everything from basic walking, swimming, knockback, cobwebs, to bubble columns
* It even covers riding entities from boats to pigs to striders
* Built upon covering edge cases to confirm accuracy
* 1.13+ clients on 1.13+ servers, 1.12- clients on 1.13+ servers, 1.13+ clients on 1.12- servers,
and 1.12- clients on 1.12- servers are all supported regardless of the large technical changes
between these versions.
* 1.13+ clients on 1.13+ servers, 1.12- clients on 1.13+ servers, 1.13+ clients on 1.12- servers, and 1.12- clients on 1.12- servers are all supported regardless of the large technical changes between these versions.
* The order of collisions depends on the client version and is correct
* Accounts for minor bounding box differences between versions, for example:
* Single glass panes will be a + shape for 1.7-1.8 players and * for 1.9+ players
@@ -106,8 +71,7 @@ What makes LightningGrim stand out against other anticheats?
* Per player, the cache allows for multithreaded design
* Sending players fake blocks with packets is safe and does not lead to falses
* The world is recreated for each player to allow lag compensation
* Client sided blocks cause no issues with packet based blocks. Block glitching does not false the
anticheat.
* Client sided blocks cause no issues with packet based blocks. Block glitching does not false the anticheat.
### Latency compensation
+14 -64
View File
@@ -1,68 +1,18 @@
/**
* GrimAC Build Configuration
*
* Build Flags:
* -PshadePE=true - Bundles PacketEvents into the jar (default: external/lite).
* -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
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."
version = "2.3.71"
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)
ext["git_commit"] = VersionUtil.getGitCommitHash(true)
ext["git_org"] = System.getenv("GRIM_GIT_ORG") ?: VersionUtil.getGitUser()
ext["git_repo"] = System.getenv("GRIM_GIT_REPO") ?: "Grim"
// Set to false for debug builds
// You cannot live reload classes if the jar relocates dependencies
// Checks Project properties -> environment variable -> defaults true
extra["relocate"] = project.findProperty("relocate")?.toString()?.toBoolean()
?: System.getenv("RELOCATE_JAR")?.toBoolean()
?: true
println("Build configuration:")
println(" shadePE = ${BuildConfig.shadePE}")
println(" relocate = ${BuildConfig.relocate}")
println(" mavenLocalOverride = ${BuildConfig.mavenLocalOverride}")
println(" release = ${BuildConfig.release}")
println(" version = $version")
tasks.register("printVersion") {
group = "versioning"
description = "Prints the computed project version"
doLast {
println("VERSION=$version")
}
}
// ---------- Java Compile Optimization ----------
subprojects {
tasks.withType<JavaCompile>().configureEach {
options.isFork = true
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")
// Build Optimization:
// - Add parallel build support
// - Add build caching
tasks.withType<JavaCompile>().configureEach {
options.isFork = true
options.isIncremental = true
}
+2 -1
View File
@@ -1,10 +1,11 @@
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 {
mavenCentral()
gradlePluginPortal()
}
dependencies {
-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)
}
}
@@ -1,5 +1,3 @@
import versioning.BuildConfig
plugins {
`java-library`
id("io.freefair.lombok")
@@ -18,6 +16,16 @@ java {
withJavadocJar()
}
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.release.set(17)
}
// Lombok configuration
//lombok {
// version.set("1.18.30") // Use the version from your version catalog if available
//}
// Spotless configuration
spotless {
java {
@@ -35,53 +43,28 @@ spotless {
}
}
tasks {
withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
options.release.set(17)
}
// Ensure spotlessApply runs before build
tasks.build {
dependsOn(tasks.named("spotlessApply"))
}
build {
// Ensure spotlessApply runs before build
dependsOn(tasks.named("spotlessApply"))
}
// Process resources (e.g., for plugin metadata files)
//tasks.processResources {
// inputs.property("version", project.version)
// filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json")) {
// expand("version" to project.version)
// }
//}
// Process resources (e.g., for plugin metadata files)
processResources {
val properties = mapOf(
"timestamp" to rootProject.ext["timestamp"],
"version" to project.version.toString(),
"git_commit" to rootProject.ext["git_commit"],
"git_branch" to rootProject.ext["git_branch"],
"git_repo" to rootProject.ext["git_repo"],
"git_org" to rootProject.ext["git_org"],
"build_shade_pe" to BuildConfig.shadePE,
"build_relocate" to BuildConfig.relocate,
"build_release" to BuildConfig.release,
)
properties.forEach { (key, value) -> inputs.property(key, value) }
filesMatching(
listOf(
"bungee.yml",
"velocity-plugin.json",
"grimac.properties"
)
) {
expand(properties)
}
}
javadoc {
title = "${rootProject.name}-${project.name} v${rootProject.version}"
options.encoding = "UTF-8"
options.overview =
rootProject.file("buildSrc/src/main/resources/javadoc-overview.html").toString()
setDestinationDir(file("${project.layout.buildDirectory.asFile.get()}/docs/javadoc"))
options {
(this as CoreJavadocOptions).addBooleanOption("Xdoclint:none", true)
}
// Javadoc configuration
tasks.javadoc {
title = "${rootProject.name}-${project.name} v${rootProject.version}"
options.encoding = "UTF-8"
options.overview =
rootProject.file("buildSrc/src/main/resources/javadoc-overview.html").toString()
setDestinationDir(file("${project.layout.buildDirectory.asFile.get()}/docs/javadoc"))
options {
(this as CoreJavadocOptions).addBooleanOption("Xdoclint:none", true)
}
}
@@ -63,7 +63,7 @@ fun parseFabricModJson(jarFile: File, project: Project): Set<DependencyIdentifie
fun extractEmbeddedJars(jarFile: File, project: Project): Set<DependencyIdentifier> {
val embeddedJars = mutableSetOf<DependencyIdentifier>()
embeddedJars.addAll(parseFabricModJson(jarFile, project)) // Retain existing logic for top-level fabric.mod.json parsing
embeddedJars.addAll(parseFabricModJson(jarFile, project))
try {
JarFile(jarFile).use { jar ->
val entries = jar.entries()
@@ -83,32 +83,33 @@ fun extractEmbeddedJars(jarFile: File, project: Project): Set<DependencyIdentifi
}
}
JarFile(tempFile).use { embeddedJar ->
val fabricModJsonEntry: ZipEntry? = embeddedJar.getEntry("fabric.mod.json")
if (fabricModJsonEntry != null) {
embeddedJar.getInputStream(fabricModJsonEntry).bufferedReader().use { reader ->
val jsonContent = reader.readText()
val jsonElement = Json.parseToJsonElement(jsonContent)
val jsonObject = jsonElement.jsonObject
// Extract id and parse into group
val id = jsonObject["id"]?.jsonPrimitive?.content
?: throw IllegalStateException("Missing 'id' in fabric.mod.json of embedded JAR: $entryName")
val idWithDots = id.replace("_", ".")
val lastDotIndex = idWithDots.lastIndexOf(".")
val group = if (lastDotIndex != -1) idWithDots.take(lastDotIndex) else ""
// Extract name and version from the JAR filename
val jarName = entryName.substringAfterLast("/").removeSuffix(".jar")
val (name, version) = parseNameAndVersionFromJarName(jarName)
?: throw IllegalStateException("Could not parse name and version from JAR filename: $jarName")
// Create DependencyIdentifier (classifier is empty for now)
val depId = DependencyIdentifier(group, name, version)
embeddedJars.add(depId)
project.logger.debug("Identified embedded dependency from fabric.mod.json and filename: {}", depId)
val manifest = embeddedJar.manifest
if (manifest != null) {
val group =
manifest.mainAttributes.getValue("Implementation-Vendor") ?: ""
val name = manifest.mainAttributes.getValue("Implementation-Title")
?: embeddedJar.name
val version =
manifest.mainAttributes.getValue("Implementation-Version") ?: ""
if (group.isNotEmpty() && name.isNotEmpty() && version.isNotEmpty()) {
embeddedJars.add(DependencyIdentifier(group, name, version))
project.logger.debug("Identified embedded dependency: $group:$name:$version")
} else {
val jarName = entryName.substringAfterLast("/")
val parts = jarName.removeSuffix(".jar").split("-")
if (parts.size >= 2) {
val inferredName = parts.dropLast(1).joinToString("-")
val inferredVersion = parts.last()
embeddedJars.add(
DependencyIdentifier(
"",
inferredName,
inferredVersion
)
)
project.logger.debug("Inferred embedded dependency from filename: :$inferredName:$inferredVersion")
}
}
} else {
project.logger.error("No fabric.mod.json found in embedded JAR: {}. Skipping.", entryName)
}
}
}
@@ -120,23 +121,6 @@ fun extractEmbeddedJars(jarFile: File, project: Project): Set<DependencyIdentifi
return embeddedJars
}
fun parseNameAndVersionFromJarName(jarName: String): Pair<String, String>? {
val versionStartRegex = Regex("-(?=\\d)")
// Use regex to find the position of the hyphen followed by a digit
val versionStartMatch = versionStartRegex.find(jarName) ?: return null
val versionStartIndex = versionStartMatch.range.first
// Split the JAR name into name and version at the version start index
val name = jarName.take(versionStartIndex)
val version = jarName.substring(versionStartIndex + 1) // Skip the hyphen
return if (name.isNotEmpty() && version.isNotEmpty()) {
Pair(name, version)
} else {
null
}
}
fun shouldExcludeDependency(
dependency: ResolvedDependency,
allEmbeddedDependencies: Set<DependencyIdentifier>,
@@ -300,7 +284,7 @@ project.afterEvaluate {
val allEmbeddedDependencies = mutableMapOf<DependencyIdentifier, File>()
val actuallyIncludedDependencies = mutableSetOf<DependencyIdentifier>()
fun collectAllEmbeddedDependenciesForAfterEvaluate(dependencies: Set<ResolvedDependency>) {
fun collectAllEmbeddedDependencies(dependencies: Set<ResolvedDependency>) {
dependencies.forEach { dep ->
val classifier = dep.moduleArtifacts.firstOrNull()?.classifier ?: ""
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}${if (classifier.isNotEmpty()) ":$classifier" else ""}"
@@ -318,13 +302,13 @@ project.afterEvaluate {
)
}
}
collectAllEmbeddedDependenciesForAfterEvaluate(dep.children)
collectAllEmbeddedDependencies(dep.children)
}
}
}
processed.clear()
collectAllEmbeddedDependenciesForAfterEvaluate(resolvedDependencies)
collectAllEmbeddedDependencies(resolvedDependencies)
processed.clear()
processDependencies(
@@ -345,7 +329,7 @@ tasks.withType<Jar>().configureEach {
val allEmbeddedDependencies = mutableMapOf<DependencyIdentifier, File>()
val actuallyIncludedDependencies = mutableSetOf<DependencyIdentifier>()
fun collectAllEmbeddedDependenciesForJar(dependencies: Set<ResolvedDependency>) {
fun collectAllEmbeddedDependencies(dependencies: Set<ResolvedDependency>) {
dependencies.forEach { dep ->
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}"
if (!processed.contains(depKey)) {
@@ -362,13 +346,13 @@ tasks.withType<Jar>().configureEach {
)
}
}
collectAllEmbeddedDependenciesForJar(dep.children)
collectAllEmbeddedDependencies(dep.children)
}
}
}
processed.clear()
collectAllEmbeddedDependenciesForJar(resolvedDependencies)
collectAllEmbeddedDependencies(resolvedDependencies)
processed.clear()
@@ -1,27 +1,32 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import versioning.BuildConfig
plugins {
id("com.gradleup.shadow")
}
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:.*"))
}
archiveFileName = "${rootProject.name}-${project.name}-${rootProject.version}.jar"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
val relocate: Boolean by rootProject.extra
if (BuildConfig.relocate) {
if (BuildConfig.shadePE) {
relocate("io.github.retrooper.packetevents", "ac.grim.grimac.shaded.io.github.retrooper.packetevents")
relocate("com.github.retrooper.packetevents", "ac.grim.grimac.shaded.com.github.retrooper.packetevents")
relocate("net.kyori", "ac.grim.grimac.shaded.kyori") // use PE's built-in adventure instead when not shading PE
}
// Create a configuration for shading dependencies from common
val shadowCommon: Configuration by project.configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
extendsFrom(project.configurations.getByName("implementation"))
}
tasks.named<ShadowJar>("shadowJar") {
minimize()
archiveFileName.set("${rootProject.name}-${project.name}-${rootProject.version}.jar")
from(shadowCommon) // Use from() instead of direct assignment
if (relocate) {
relocate(
"io.github.retrooper.packetevents",
"ac.grim.grimac.shaded.io.github.retrooper.packetevents"
)
relocate(
"com.github.retrooper.packetevents",
"ac.grim.grimac.shaded.com.github.retrooper.packetevents"
)
relocate("club.minnced", "ac.grim.grimac.shaded.discord-webhooks")
relocate("org.slf4j", "ac.grim.grimac.shaded.slf4j") // Required by discord-webhooks
relocate("github.scarsz.configuralize", "ac.grim.grimac.shaded.configuralize")
@@ -29,6 +34,7 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("com.google.code.gson", "ac.grim.grimac.shaded.gson")
relocate("alexh", "ac.grim.grimac.shaded.maps")
relocate("it.unimi.dsi.fastutil", "ac.grim.grimac.shaded.fastutil")
relocate("net.kyori", "ac.grim.grimac.shaded.kyori")
relocate("okhttp3", "ac.grim.grimac.shaded.okhttp3")
relocate("okio", "ac.grim.grimac.shaded.okio")
relocate("org.yaml.snakeyaml", "ac.grim.grimac.shaded.snakeyaml")
@@ -37,8 +43,6 @@ tasks.named<ShadowJar>("shadowJar") {
relocate("org.jetbrains", "ac.grim.grimac.shaded.jetbrains")
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()
}
@@ -1,96 +0,0 @@
package versioning
import org.gradle.api.Project
import org.gradle.internal.extensions.stdlib.toDefaultLowerCase
import versioning.BuildConfig.init
import versioning.BuildConfig.mavenLocalOverride
import versioning.BuildConfig.release
import versioning.BuildConfig.relocate
import versioning.BuildConfig.shadePE
/**
* BuildConfig provides access to user-defined build flags that control how a Grim
* build is assembled. These flags are resolved once at configuration time using
* [init], and are then exposed as fast, memoized values.
*
* Flags can be defined in three ways:
*
* 1. JVM system properties (-Dflag=value)
* 2. Gradle project properties (-Pflag=value or in gradle.properties)
* 3. Environment variables (FLAG=value)
*
* You can use these to enable/disable features like shading, relocation, or release mode.
*
* Examples:
*
* Using Gradle -P properties:
* ```
* ./gradlew build -PshadePE=true -Prelocate=false -Prelease=true
* ```
*
* Using environment variables:
* ```
* SHADE_PE=true RELOCATE_JAR=false RELEASE=true ./gradlew build
* ```
*
* Using JVM system properties:
* ```
* ./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 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
*/
object BuildConfig {
/**
* Must be called once from your root build script to initialize the flags.
* Example (in build.gradle.kts):
* ```
* BuildConfig.init(project)
* ```
*/
fun init(project: Project) {
_shadePE = resolveBool(project, "shadePE", altKey = "SHADE_PE", default = false)
_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)
}
// Unified resolution logic (System > Gradle > Env)
private fun resolveRaw(project: Project, key: String): String? =
System.getProperty(key) // 1. JVM (-Dkey=value)
?: project.findProperty(key)?.toString() // 2. Gradle (-Pkey=value or gradle.properties)
?: System.getenv(key.uppercase()) // 3. ENV (KEY=value)
private fun resolveBool(project: Project, key: String, altKey: String? = null, default: Boolean): Boolean {
return resolveRaw(project, key)?.toDefaultLowerCase()?.toBooleanStrictOrNull()
?: altKey?.let {
resolveRaw(project, it)?.toDefaultLowerCase()?.toBooleanStrictOrNull()
} ?: default
}
// Private backing vars (nullable because we can't use lateinit with primitives)
private var _shadePE: Boolean? = null
private var _relocate: Boolean? = null
private var _release: Boolean? = null
private var _mavenLocalOverride: Boolean? = null
/** If true, shades PacketEvents into the jar. Default: false (external provider). */
val shadePE: Boolean get() = _shadePE
?: error("BuildConfig.shadePE accessed before init() was called")
/** If true, relocates shaded dependencies to avoid conflicts. Default: true. */
val relocate: Boolean get() = _relocate
?: error("BuildConfig.relocate accessed before init() was called")
/** If true, omits commit hash and modifiers from version string. Default: false. */
val release: Boolean get() = _release
?: error("BuildConfig.release accessed before init() was called")
val mavenLocalOverride: Boolean get() = _mavenLocalOverride
?: error("BuildConfig.release accessed before init() was called")
}
@@ -1,114 +0,0 @@
package versioning
import java.io.ByteArrayOutputStream
/**
* Utility for computing the version string of LightningGrim artifacts.
*
* The version string is constructed based on:
* - A base semantic version (e.g., "2.3.72")
* - Git commit hash (unless in release mode)
* - Git branch name (unless in release mode or main)
* - Modifiers (e.g., lite, no_relocate) for non-default build configurations
*
* Example outputs:
* - `2.3.72` (release build)
* - `2.3.72-a4f8b21+lite` (preview build without PE shading)
* - `2.3.72-feature_branch-a4f8b21+lite-no_relocate`
*
* @see BuildConfig for controlling the release/modifier behavior
*/
object VersionUtil {
/**
* Computes the full version string for the build.
*
* @param baseVersion The base semantic version (e.g., "2.3.72")
* @return Full version string including commit hash, branch, and modifiers if applicable
*/
fun computeVersion(baseVersion: String): String {
if (BuildConfig.release) {
return baseVersion
}
val commitHash = getGitCommitHash()
val branch = getGitBranch()
val modifiers = buildList {
if (!BuildConfig.shadePE) add("lite")
if (!BuildConfig.relocate) add("no_relocate")
}.joinToString("-").takeIf { it.isNotEmpty() }
return buildString {
append(baseVersion)
append("-")
branch?.let { append("$it-") }
append(commitHash)
modifiers?.let { append("+$it") }
}
}
/**
* Retrieves the current Git commit
*/
fun getGitCommitHash(full: Boolean = false): String {
val stdout = ByteArrayOutputStream()
val command = listOfNotNull("git", "rev-parse", if (full) null else "--short", "HEAD")
ProcessBuilder(command)
.redirectErrorStream(true)
.start()
.apply { waitFor() }
.inputStream
.use { stdout.writeBytes(it.readAllBytes()) }
val fullCommit = stdout.toString().trim()
return fullCommit.take(minOf(fullCommit.length, 7))
}
/**
* Returns the current Git branch, sanitised for use in file names.
* If the branch is "main" or "2.0", returns null.
*
* Any slash (/) in the branch name is replaced with an underscore (_)
* to avoid filesystem issues.
*/
fun getGitBranch(raw: Boolean = false): String? {
val stdout = ByteArrayOutputStream()
ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
.redirectErrorStream(true)
.start()
.apply { waitFor() }
.inputStream.use { stdout.writeBytes(it.readAllBytes()) }
if (raw) return stdout.toString().trim()
val branch = stdout.toString().trim()
.replace(Regex("[^a-zA-Z0-9_.-]+"), "_")
.replace(Regex("_{2,}"), "_")
.trim(' ', '.', '_', '-')
.removePrefix("heads_")
val mainBranch = System.getenv("GRIM_MAIN_BRANCH") ?: "2.0"
return when (branch) {
"lightning" -> null
"main", mainBranch -> null // ← ignore these branches
else -> branch
}
}
fun getGitUser(): String {
try {
val stdout = ByteArrayOutputStream()
ProcessBuilder("git", "config", "user.name")
.redirectErrorStream(true)
.start()
.apply { waitFor() }
.inputStream.use { stdout.writeBytes(it.readAllBytes()) }
return stdout.toString().trim()
} catch (_: Exception) {
return "unknown"
}
}
}
+30 -77
View File
@@ -1,77 +1,56 @@
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription.Permission
import versioning.BuildConfig
plugins {
`maven-publish`
grim.`base-conventions`
grim.`shadow-conventions`
id("de.eldoria.plugin-yml.bukkit") version "0.8.0"
id("xyz.jpenilla.run-paper") version "3.0.0-beta.1"
id("net.minecrell.plugin-yml.bukkit") version "0.6.0"
}
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")
mavenLocal()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
exclusive("https://libraries.minecraft.net", { mavenContent { releasesOnly() } }) {
includeModule("com.mojang", "brigadier")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // Spigot
maven("https://jitpack.io/") { // Grim API
content {
includeGroup("com.github.grimanticheat")
}
}
exclusive("https://repo.extendedclip.com/content/repositories/placeholderapi/") {
includeGroup("me.clip")
}
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
includeGroup("com.github.retrooper")
}
exclusive("https://repo.grim.ac/snapshots") {
includeGroup("ac.grim.grimac")
}
exclusive("https://nexus.scarsz.me/content/repositories/releases", { mavenContent { releasesOnly() } }) {
includeGroup("github.scarsz")
}
maven("https://repo.viaversion.com") // ViaVersion
maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate)
maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents
maven("https://repo.codemc.io/repository/maven-snapshots/")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // placeholderapi
mavenCentral()
// FastUtil, Discord-Webhooks
}
dependencies {
compileOnly(libs.paper.api)
compileOnly(libs.placeholderapi)
if (BuildConfig.shadePE) {
implementation(libs.packetevents.spigot)
} else {
compileOnly(libs.packetevents.spigot)
}
implementation(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")
}
softDepend = listOf(
"ProtocolLib",
"ProtocolSupport",
@@ -83,7 +62,6 @@ bukkit {
"floodgate",
"FastLogin",
"PlaceholderAPI",
"ZNPCsPlus",
)
permissions {
@@ -138,49 +116,24 @@ bukkit {
}
register("grim.verbose") {
description = "Receive verbose alerts for violations"
description = "Receive verbose alerts for violations. Requires grim.alerts"
default = Permission.Default.OP
}
register("grim.verbose.enable-on-join") {
description =
"Enable verbose alerts on join"
default = Permission.Default.FALSE
}
register("grim.list") {
description =
"Shows lists of specific data"
"Enable verbose alerts on join. Requires grim.alerts and grim.alerts.enable-on-join"
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)
}
}
shadowJar {
mergeServiceFiles()
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
tasks.shadowJar {
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
@@ -20,7 +20,6 @@ public class BukkitPlatformPlugin implements PlatformPlugin {
return plugin.getName();
}
@SuppressWarnings("deprecation")
@Override
public String getVersion() {
return plugin.getDescription().getVersion();
@@ -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
@@ -29,16 +26,12 @@ public class BukkitPlatformServer implements PlatformServer {
}
@Override
public void registerOutgoingPluginChannel(String name) {
GrimACBukkitLoaderPlugin.LOADER.getServer().getMessenger().registerOutgoingPluginChannel(GrimACBukkitLoaderPlugin.LOADER, name);
public void registerOutgoingPluginChannel(String bungeeCord) {
GrimACBukkitLoaderPlugin.LOADER.getServer().getMessenger().registerOutgoingPluginChannel(GrimACBukkitLoaderPlugin.LOADER, "BungeeCord");
}
@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,21 @@
package ac.grim.grimac.platform.bukkit;
import ac.grim.grimac.api.plugin.BasicGrimPlugin;
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.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.ParserDescriptorFactory;
import ac.grim.grimac.platform.api.manager.PermissionRegistrationManager;
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;
@@ -37,51 +33,46 @@ 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.anticheat.MessageUtil;
import ac.grim.grimac.utils.lazy.LazyHolder;
import com.github.retrooper.packetevents.PacketEventsAPI;
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.brigadier.BrigadierSetting;
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.jetbrains.annotations.NotNull;
public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements PlatformLoader {
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 PlatformPluginManager pluginManager = new BukkitPlatformPluginManager();
@Getter private final GrimPlugin plugin;
@Getter private final PlatformServer platformServer = new BukkitPlatformServer();
@Getter private final MessagePlaceHolderManager messagePlaceHolderManager = new BukkitMessagePlaceHolderManager();
@Getter private final BukkitPermissionRegistrationManager permissionManager = new BukkitPermissionRegistrationManager();
private final PlatformPlayerFactory playerFactory = new BukkitPlatformPlayerFactory();
private final ParserDescriptorFactory parserFactory = new BukkitParserDescriptorFactory();
private final PlatformPluginManager platformPluginManager = new BukkitPlatformPluginManager();
private final GrimPlugin plugin;
private final PlatformServer platformServer = new BukkitPlatformServer();
private final MessagePlaceHolderManager messagePlaceHolderManager = new BukkitMessagePlaceHolderManager();
private final BukkitPermissionRegistrationManager bukkitPermissionRegistrationManager = new BukkitPermissionRegistrationManager();
public GrimACBukkitLoaderPlugin() {
BukkitResolverRegistrar registrar = new BukkitResolverRegistrar();
registrar.registerAll(GrimAPI.INSTANCE.getExtensionManager());
this.plugin = registrar.resolvePlugin(this);
this.plugin = new BasicGrimPlugin(
this.getLogger(),
this.getDataFolder(),
this.getDescription().getVersion(),
this.getDescription().getDescription(),
this.getDescription().getAuthors()
);
}
@Override
@@ -91,13 +82,13 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
}
private Initable[] getBukkitInitTasks() {
return new Initable[] {
return new Initable[]{
new ExemptOnlinePlayersOnReload(),
new BukkitEventManager(),
new BukkitTickEndEvent(),
new BukkitBStats(),
(StartableInitable) () -> {
if (BukkitMessagePlaceHolderManager.hasPlaceholderAPI) {
if (MessageUtil.hasPlaceholderAPI) {
new PlaceholderAPIExpansion().register();
}
}
@@ -119,133 +110,79 @@ public final class GrimACBukkitLoaderPlugin extends JavaPlugin implements Platfo
return scheduler.get();
}
@Override
public PlatformPlayerFactory getPlatformPlayerFactory() {
return playerFactory;
}
@Override
public ParserDescriptorFactory getParserDescriptorFactory() {
return parserFactory;
}
@Override
public PacketEventsAPI<?> getPacketEvents() {
return packetEvents.get();
}
@Override
public CommandManager<Sender> getCommandManager() {
return commandManager.get();
}
@Override
public ItemResetHandler getItemResetHandler() {
return itemResetHandler.get();
}
@Override
public CommandService getCommandService() {
return commandService.get();
}
@Override
public SenderFactory<CommandSender> getSenderFactory() {
return senderFactory.get();
}
@Override
public GrimPlugin getPlugin() {
return plugin;
}
@Override
public PlatformPluginManager getPluginManager() {
return platformPluginManager;
}
@Override
public PlatformServer getPlatformServer() {
return platformServer;
}
@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();
GrimAPIProvider.init(GrimAPI.INSTANCE.getExternalAPI());
Bukkit.getServicesManager().register(GrimAbstractAPI.class, GrimAPI.INSTANCE.getExternalAPI(), GrimACBukkitLoaderPlugin.LOADER, ServicePriority.Normal);
}
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());
@Override
public @NotNull MessagePlaceHolderManager getMessagePlaceHolderManager() {
return messagePlaceHolderManager;
}
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);
@Override
public PermissionRegistrationManager getPermissionManager() {
return bukkitPermissionRegistrationManager;
}
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();
} else if (manager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
manager.registerAsynchronousCompletions();
}
@@ -4,15 +4,16 @@ import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.command.PlayerSelector;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.bukkit.sender.BukkitSenderFactory;
import lombok.RequiredArgsConstructor;
import org.incendo.cloud.bukkit.data.SinglePlayerSelector;
import java.util.Collection;
import java.util.Collections;
@RequiredArgsConstructor
public class BukkitPlayerSelectorAdapter implements PlayerSelector {
private final SinglePlayerSelector bukkitSelector;
private final org.incendo.cloud.bukkit.data.SinglePlayerSelector bukkitSelector;
public BukkitPlayerSelectorAdapter(org.incendo.cloud.bukkit.data.SinglePlayerSelector bukkitSelector) {
this.bukkitSelector = bukkitSelector;
}
@Override
public boolean isSingle() {
@@ -6,24 +6,21 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.platform.bukkit.world.BukkitPlatformWorld;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.google.common.base.Preconditions;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BukkitGrimEntity implements GrimEntity {
protected static final boolean CAN_USE_DIRECT_GETTERS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_20_1) && PaperUtils.PAPER;
private final Entity entity;
private BukkitPlatformWorld bukkitPlatformWorld;
public BukkitGrimEntity(Entity entity) {
this.entity = Objects.requireNonNull(entity);
Preconditions.checkArgument(entity != null);
this.entity = entity;
}
public Entity getBukkitEntity() {
@@ -47,7 +44,7 @@ public class BukkitGrimEntity implements GrimEntity {
}
@Override
@NotNull
@NonNull
public Entity getNative() {
return entity;
}
@@ -63,6 +60,7 @@ public class BukkitGrimEntity implements GrimEntity {
if (bukkitPlatformWorld == null || !bukkitPlatformWorld.getBukkitWorld().equals(entity.getWorld())) {
bukkitPlatformWorld = new BukkitPlatformWorld(entity.getWorld());
}
return bukkitPlatformWorld;
}
@@ -78,26 +76,4 @@ public class BukkitGrimEntity implements GrimEntity {
location.getPitch()
);
}
@Override
public double distanceSquared(double oX, double oY, double oZ) {
if (CAN_USE_DIRECT_GETTERS) {
double x = this.entity.getX();
double y = this.entity.getY();
double z = this.entity.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
} else {
org.bukkit.Location location = this.entity.getLocation();
double x = location.getX();
double y = location.getY();
double z = location.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
}
}
}
@@ -1,10 +1,10 @@
package ac.grim.grimac.platform.bukkit.events;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.blockstate.BukkitBlockFaceHelper;
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 org.bukkit.Material;
import org.bukkit.block.Block;
@@ -18,19 +18,8 @@ import java.util.ArrayList;
import java.util.List;
public class PistonEvent implements Listener {
private final Material SLIME_BLOCK = Material.getMaterial("SLIME_BLOCK");
private final Material HONEY_BLOCK = Material.getMaterial("HONEY_BLOCK");
private static final double MAX_HORIZONTAL_DISTANCE = 24.0;
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;
}
Material SLIME_BLOCK = Material.getMaterial("SLIME_BLOCK");
Material HONEY_BLOCK = Material.getMaterial("HONEY_BLOCK");
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPistonPushEvent(BlockPistonExtendEvent event) {
@@ -66,8 +55,12 @@ 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());
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) {
PistonData data = new PistonData(BukkitBlockFaceHelper.fromBukkitFace(event.getDirection()), boxes, player.lastTransactionSent.get(), true, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(player.lastTransactionSent.get(), () -> player.compensatedWorld.activePistons.add(data));
}
}
}
// For some unknown reason, bukkit handles this stupidly
@@ -86,11 +79,12 @@ public class PistonEvent implements Listener {
boolean hasHoneyBlock = false;
List<SimpleCollisionBox> boxes = new ArrayList<>();
BlockFace face = BukkitConversionUtils.fromBukkitFace(event.getDirection());
BlockFace face = BukkitBlockFaceHelper.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 +108,11 @@ public class PistonEvent implements Listener {
}
}
PistonTemplate data = new PistonTemplate(face, boxes, false, hasSlimeBlock, hasHoneyBlock);
addPistonData(data, piston.getX(), piston.getY(), piston.getZ());
}
private void addPistonData(PistonTemplate pistonTemplate, int blockX, int blockY, int blockZ) {
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(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 (player.compensatedWorld.isChunkLoaded(event.getBlock().getX() >> 4, event.getBlock().getZ() >> 4)) {
PistonData data = new PistonData(BukkitBlockFaceHelper.fromBukkitFace(event.getDirection()), boxes, player.lastTransactionSent.get(), false, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(player.lastTransactionSent.get(), () -> player.compensatedWorld.activePistons.add(data));
}
}
}
}
@@ -2,14 +2,15 @@ package ac.grim.grimac.platform.bukkit.initables;
import ac.grim.grimac.manager.init.start.StartableInitable;
import ac.grim.grimac.platform.bukkit.GrimACBukkitLoaderPlugin;
import ac.grim.grimac.utils.anticheat.Constants;
import io.github.retrooper.packetevents.bstats.bukkit.Metrics;
public class BukkitBStats implements StartableInitable {
@Override
public void start() {
int pluginId = 12820; // <-- Replace with the id of your plugin!
try {
new Metrics(GrimACBukkitLoaderPlugin.LOADER, Constants.BSTATS_PLUGIN_ID);
} catch (Exception ignored) {}
new Metrics(GrimACBukkitLoaderPlugin.LOADER, pluginId);
} catch (Exception ignored) {
}
}
}
@@ -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);
@@ -111,7 +86,7 @@ public class BukkitTickEndEvent extends AbstractTickEndEvent implements Listener
Unsafe unsafe = (Unsafe) unsafeField.get(null);
unsafe.putObject(connection, unsafe.objectFieldOffset(connectionsList), wrapper);
} catch (NoSuchFieldException | IllegalAccessException e) {
LogUtil.error("Failed to inject into the end of tick event via reflection", e);
LogUtil.exception("Failed to inject into the end of tick event via reflection", e);
return false;
}
return true;
@@ -2,153 +2,79 @@ package ac.grim.grimac.platform.bukkit.manager;
import ac.grim.grimac.platform.api.manager.ItemResetHandler;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.bukkit.player.BukkitPlatformPlayer;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
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.protocol.player.InteractionHand;
import lombok.SneakyThrows;
import org.bukkit.Bukkit;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.EquipmentSlot;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class BukkitItemResetHandler implements ItemResetHandler {
private static final Consumer<Player> resetItemUsage;
private static final Predicate<Player> isUsingItem;
private static final Function<Player, InteractionHand> getItemUsageHand;
// resets item usage, then returns whether the player was using an item
private final @NotNull ItemUsageReset resetItemUsage = createItemUsageResetFunction();
@Override
@SneakyThrows
public void resetItemUsage(@Nullable PlatformPlayer player) {
if (player != null) resetItemUsage.accept((Player) player.getNative());
if (player != null) {
resetItemUsage.accept(((BukkitPlatformPlayer) player).getNative());
}
}
@Override
public @Nullable InteractionHand getItemUsageHand(@Nullable PlatformPlayer player) {
return player == null ? null : getItemUsageHand.apply((Player) player.getNative());
}
@Override
public boolean isUsingItem(@Nullable PlatformPlayer player) {
return player != null && isUsingItem.test((Player) player.getNative());
}
static {
final ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
boolean legacy = version.isOlderThanOrEquals(ServerVersion.V_1_8_8);
try {
Method getHandle_ = null;
if (PaperUtils.PAPER && version.isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
Class<?> cls = ReflectionUtils.getClass("org.bukkit.craftbukkit.entity.CraftLivingEntity");
if (cls != null) getHandle_ = cls.getMethod("getHandle");
@SneakyThrows
private @NotNull ItemUsageReset createItemUsageResetFunction() {
ServerVersion version = PacketEvents.getAPI().getServerManager().getVersion();
if (version.isNewerThan(ServerVersion.V_1_17) && PaperUtils.PAPER) {
if (version.isOlderThan(ServerVersion.V_1_19)) {
return LivingEntity::clearActiveItem;
}
Method setLivingEntityFlag = Class.forName(version.isOlderThan(ServerVersion.V_1_20_5) ? "net.minecraft.world.entity.EntityLiving" : "net.minecraft.world.entity.LivingEntity")
.getDeclaredMethod(version.isOlderThan(ServerVersion.V_1_20_5) ? "c" : "setLivingEntityFlag", int.class, boolean.class);
Method getHandle = (version.isOlderThan(ServerVersion.V_1_20_5)
? Class.forName("org.bukkit.craftbukkit." + Bukkit.getServer().getClass().getPackageName().split("\\.")[3] + ".entity.CraftPlayer")
: Class.forName("org.bukkit.craftbukkit.entity.CraftPlayer")
).getMethod("getHandle");
boolean obfuscated = getHandle_ == null;
String nmsPackage = obfuscated ? Bukkit.getServer().getClass().getPackageName().split("\\.")[3] : null;
setLivingEntityFlag.setAccessible(true);
if (obfuscated) {
String clazzName = legacy ? "CraftHumanEntity" : "CraftLivingEntity";
getHandle_ = Class.forName("org.bukkit.craftbukkit." + nmsPackage + ".entity." + clazzName).getMethod("getHandle");
}
return player -> {
// don't trigger gameevents
setLivingEntityFlag.invoke(getHandle.invoke(player), 1, false);
player.clearActiveItem();
};
}
final Method getHandle = getHandle_;
Class<?> clazz = getHandle_.getReturnType();
if (version == ServerVersion.V_1_8_8) {
Class<?> EntityHuman = Class.forName("net.minecraft.server.v1_8_R3.EntityHuman");
Method getHandle = Class.forName("org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer").getMethod("getHandle");
Method clearActiveItem = EntityHuman.getMethod("bV");
Method isUsingItem = EntityHuman.getMethod("bS");
if (version.isNewerThanOrEquals(ServerVersion.V_1_10)) {
isUsingItem = Player::isHandRaised;
} else {
Method method = clazz.getMethod(switch (Objects.requireNonNull(nmsPackage)) {
case "v1_8_R3" -> "bS";
case "v1_9_R1" -> "cs";
case "v1_9_R2" -> "ct";
default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")");
});
isUsingItem = player -> {
try {
return (boolean) method.invoke(getHandle.invoke(player));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
}
return player -> {
Object handle = getHandle.invoke(player);
clearActiveItem.invoke(handle);
if (legacy) {
getItemUsageHand = player -> isUsingItem.test(player) ? InteractionHand.MAIN_HAND : null;
} else if (PaperUtils.PAPER && version.isNewerThanOrEquals(ServerVersion.V_1_16_5)) {
getItemUsageHand = player -> player.isHandRaised()
? player.getHandRaised() == EquipmentSlot.OFF_HAND
? InteractionHand.OFF_HAND
: InteractionHand.MAIN_HAND
: null;
} else {
Method method = clazz.getMethod(switch (Objects.requireNonNull(nmsPackage)) {
case "v1_9_R1" -> "ct";
case "v1_9_R2" -> "cu";
case "v1_10_R1" -> "cy";
case "v1_11_R1" -> "cz";
case "v1_12_R1" -> "cH";
case "v1_13_R1", "v1_13_R2", "v1_14_R1" -> "cU";
case "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "getRaisedHand";
case "v1_18_R1" -> "eM";
case "v1_18_R2" -> "eN";
case "v1_19_R1" -> "eU";
case "v1_19_R2" -> "fa";
case "v1_19_R3" -> "ff";
case "v1_20_R1" -> "fj";
case "v1_20_R2" -> "fn";
case "v1_20_R3" -> "fo";
case "v1_20_R4" -> "fw";
case "v1_21_R1" -> "fs";
case "v1_21_R2", "v1_21_R3", "v1_21_R4" -> "fA";
case "v1_21_R5" -> "fH";
case "v1_21_R6" -> "fP";
default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")");
});
getItemUsageHand = player -> {
try {
return isUsingItem.test(player)
? ((Enum<?>) method.invoke(getHandle.invoke(player))).ordinal() == 0
? InteractionHand.MAIN_HAND
: InteractionHand.OFF_HAND
: null;
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
}
// in 1.8 we need to resync item usage manually,
// only do so if the player was using an item
if ((boolean) isUsingItem.invoke(handle)) {
player.updateInventory();
}
};
}
Method setLivingEntityFlag;
if (version.isNewerThanOrEquals(ServerVersion.V_1_19)) {
String name = obfuscated ? "c" : "setLivingEntityFlag";
setLivingEntityFlag = clazz.getDeclaredMethod(name, int.class, boolean.class);
setLivingEntityFlag.setAccessible(true);
} else {
setLivingEntityFlag = null;
}
if (PaperUtils.PAPER && version.isNewerThan(ServerVersion.V_1_17)) {
resetItemUsage = setLivingEntityFlag == null ? LivingEntity::clearActiveItem : player -> {
try {
setLivingEntityFlag.invoke(getHandle.invoke(player), 1, false);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
player.clearActiveItem();
};
} else {
Method method = clazz.getMethod(switch (Objects.requireNonNull(nmsPackage)) {
case "v1_8_R3" -> "bV";
String nmsPackage = Bukkit.getServer().getClass().getPackageName().split("\\.")[3];
String livingEntityPackage = version.isNewerThan(ServerVersion.V_1_16_5) ? "net.minecraft.world.entity.EntityLiving" : "net.minecraft.server." + nmsPackage + ".EntityLiving";
Method getHandle = Class.forName("org.bukkit.craftbukkit." + nmsPackage + ".entity.CraftPlayer").getMethod("getHandle");
Method clearActiveItem = Class.forName(livingEntityPackage).getMethod(
switch (nmsPackage) {
case "v1_9_R1" -> "cz";
case "v1_9_R2" -> "cA";
case "v1_10_R1" -> "cE";
@@ -160,54 +86,15 @@ public class BukkitItemResetHandler implements ItemResetHandler {
case "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1" -> "clearActiveItem";
case "v1_18_R1" -> "eR";
case "v1_18_R2" -> "eS";
case "v1_19_R1" -> "eZ";
case "v1_19_R2" -> "ff";
case "v1_19_R3" -> "fk";
case "v1_20_R1" -> "fo";
case "v1_20_R2" -> "fs";
case "v1_20_R3" -> "ft";
case "v1_20_R4" -> "fB";
case "v1_21_R1" -> "fx";
case "v1_21_R2", "v1_21_R3", "v1_21_R4" -> "fF";
case "v1_21_R5" -> "fM";
case "v1_21_R6" -> "fU";
default -> throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")");
});
if (legacy) { // 1.8.8
resetItemUsage = player -> {
try {
method.invoke(getHandle.invoke(player));
// in 1.8 we need to resync item usage manually,
// only do so if the player is using an item
if (isUsingItem.test(player)) player.updateInventory();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
} else if (setLivingEntityFlag == null) { // 1.9-1.18.2
resetItemUsage = player -> {
try {
method.invoke(getHandle.invoke(player));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
} else { // 1.19+
resetItemUsage = player -> {
try {
Object handle = getHandle.invoke(player);
setLivingEntityFlag.invoke(handle, 1, false);
method.invoke(handle);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
};
default ->
throw new IllegalStateException("You are using an unsupported server version! (" + version.getReleaseName() + ")");
}
}
} catch (Throwable t) {
throw t instanceof RuntimeException e ? e : new RuntimeException(t);
}
);
return player -> clearActiveItem.invoke(getHandle.invoke(player));
}
private interface ItemUsageReset {
void accept(@NotNull Player player) throws Throwable;
}
}
@@ -2,18 +2,27 @@ package ac.grim.grimac.platform.bukkit.manager;
import ac.grim.grimac.platform.api.manager.MessagePlaceHolderManager;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.bukkit.GrimACBukkitLoaderPlugin;
import ac.grim.grimac.platform.bukkit.player.BukkitPlatformPlayer;
import com.github.retrooper.packetevents.util.reflection.Reflection;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import me.clip.placeholderapi.PlaceholderAPI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BukkitMessagePlaceHolderManager implements MessagePlaceHolderManager {
public static final boolean hasPlaceholderAPI = Reflection.getClassByNameWithoutException("me.clip.placeholderapi.PlaceholderAPI") != null;
@Override
public @NonNull String replacePlaceholders(@Nullable Sender sender, @NonNull String string) {
if (!MessageUtil.hasPlaceholderAPI) return string;
CommandSender commandSender = sender == null ? null : GrimACBukkitLoaderPlugin.LOADER.getBukkitSenderFactory().unwrap(sender);
return PlaceholderAPI.setPlaceholders(sender instanceof OfflinePlayer ? (OfflinePlayer) commandSender : null, string);
}
@Override
public @NotNull String replacePlaceholders(@Nullable PlatformPlayer player, @NotNull String string) {
if (!hasPlaceholderAPI) return string;
return PlaceholderAPI.setPlaceholders(player instanceof BukkitPlatformPlayer bukkitPlatformPlayer ? bukkitPlatformPlayer.getBukkitPlayer() : null, string);
public @NonNull String replacePlaceholders(@Nullable PlatformPlayer object, @NonNull String string) {
if (!MessageUtil.hasPlaceholderAPI) return string;
return PlaceholderAPI.setPlaceholders(((BukkitPlatformPlayer) object).getBukkitPlayer(), string);
}
}
@@ -1,45 +1,17 @@
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.ParserDescriptorFactory;
import ac.grim.grimac.platform.api.sender.Sender;
import ac.grim.grimac.platform.bukkit.command.BukkitPlayerSelectorParser;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.bukkit.BukkitCommandContextKeys;
import org.incendo.cloud.parser.ParserDescriptor;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class BukkitParserDescriptorFactory implements ParserDescriptorFactory {
public class BukkitParserDescriptorFactory implements CloudCommandAdapter {
// Parser is kept as a field because its stateless
private final BukkitPlayerSelectorParser<Sender> bukkitPlayerSelectorParser = new BukkitPlayerSelectorParser<>();
BukkitPlayerSelectorParser<Sender> bukkitPlayerSelectorParser = new BukkitPlayerSelectorParser<>();
@Override
public ParserDescriptor<Sender, PlayerSelector> singlePlayerSelectorParser() {
public ParserDescriptor<Sender, PlayerSelector> getSinglePlayer() {
return bukkitPlayerSelectorParser.descriptor();
}
// TODO (Cross-platform) better + brigadier suggestions
@Override
public SuggestionProvider<Sender> onlinePlayerSuggestions() {
return (context, input) -> {
List<Suggestion> suggestions = new ArrayList<>();
for(Player player : Bukkit.getOnlinePlayers()) {
CommandSender bukkit = context.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER);
if (!(bukkit instanceof Player) || ((Player)bukkit).canSee(player)) {
suggestions.add(Suggestion.suggestion(player.getName()));
}
}
return CompletableFuture.completedFuture(suggestions);
};
}
}
@@ -18,16 +18,16 @@ public class BukkitPermissionRegistrationManager implements PermissionRegistrati
* and permission checks immediately on startup. If the permission already exists, its default
* value is updated to match the specified value.</p>
*
* @param name The permission node to register (e.g., "grim.exempt.checkname").
* @param s The permission node to register (e.g., "grim.exempt.checkname").
* @param defaultValue The default value for the permission.
*/
@Override
public void registerPermission(String name, PermissionDefaultValue defaultValue) {
final Permission bukkitPermission = Bukkit.getPluginManager().getPermission(name);
if (bukkitPermission == null) {
Bukkit.getPluginManager().addPermission(new Permission(name, BukkitConversionUtils.toBukkitPermissionDefault(defaultValue)));
public void registerPermission(String s, PermissionDefaultValue defaultValue) {
final Permission permission = Bukkit.getPluginManager().getPermission(s);
if (permission == null) {
Bukkit.getPluginManager().addPermission(new Permission(s, BukkitConversionUtils.toBukkitPermissionDefault(defaultValue)));
} else {
bukkitPermission.setDefault(BukkitConversionUtils.toBukkitPermissionDefault(defaultValue));
permission.setDefault(BukkitConversionUtils.toBukkitPermissionDefault(defaultValue));
}
}
}
@@ -1,14 +1,29 @@
package ac.grim.grimac.platform.bukkit.manager;
import ac.grim.grimac.api.event.EventBus;
import ac.grim.grimac.api.event.GrimEvent;
import ac.grim.grimac.api.event.OptimizedEventBus;
import ac.grim.grimac.platform.api.PlatformPlugin;
import ac.grim.grimac.platform.api.manager.PlatformPluginManager;
import ac.grim.grimac.platform.bukkit.BukkitPlatformPlugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BukkitPlatformPluginManager implements PlatformPluginManager {
private final EventBus eventBus = new OptimizedEventBus();
@Override
public void callEvent(GrimEvent event) {
eventBus.post(event);
}
@Override
public EventBus getEventBus() {
return this.eventBus;
}
@Override
public PlatformPlugin[] getPlugins() {
Plugin[] bukkitPlugins = Bukkit.getPluginManager().getPlugins();
@@ -26,4 +41,10 @@ public class BukkitPlatformPluginManager implements PlatformPluginManager {
Plugin bukkitPlugin = Bukkit.getPluginManager().getPlugin(pluginName);
return bukkitPlugin == null ? null : new BukkitPlatformPlugin(bukkitPlugin);
}
@Override
public boolean isPluginEnabled(String pluginName) {
PlatformPlugin plugin = getPlugin(pluginName);
return plugin != null && plugin.isEnabled();
}
}
@@ -1,34 +0,0 @@
package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import lombok.RequiredArgsConstructor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.UUID;
@RequiredArgsConstructor
public class BukkitOfflinePlatformPlayer implements OfflinePlatformPlayer {
private final OfflinePlayer offlinePlayer;
@Override
public boolean isOnline() {
return offlinePlayer.isOnline();
}
@Override
public @NotNull String getName() {
return Objects.requireNonNull(offlinePlayer.getName());
}
@Override
public @NotNull UUID getUniqueId() {
return offlinePlayer.getUniqueId();
}
@Override
public boolean equals(Object o) {
return o instanceof OfflinePlatformPlayer player && this.getUniqueId().equals(player.getUniqueId());
}
}
@@ -3,14 +3,16 @@ package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.player.PlatformInventory;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.RequiredArgsConstructor;
import org.bukkit.entity.Player;
@RequiredArgsConstructor
public class BukkitPlatformInventory implements PlatformInventory {
private final Player bukkitPlayer;
public BukkitPlatformInventory(Player bukkitPlayer) {
this.bukkitPlayer = bukkitPlayer;
}
@Override
public ItemStack getItemInHand() {
return SpigotConversionUtil.fromBukkitItemStack(bukkitPlayer.getInventory().getItemInHand());
@@ -33,7 +35,7 @@ public class BukkitPlatformInventory implements PlatformInventory {
@Override
public ItemStack getChestplate() {
return SpigotConversionUtil.fromBukkitItemStack(bukkitPlayer.getInventory().getChestplate());
return SpigotConversionUtil.fromBukkitItemStack(bukkitPlayer.getInventory().getHelmet());
}
@Override
@@ -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;
@@ -10,44 +9,32 @@ import ac.grim.grimac.platform.bukkit.entity.BukkitGrimEntity;
import ac.grim.grimac.platform.bukkit.utils.anticheat.MultiLibUtil;
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.Vector3d;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.Getter;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPlayer {
private static final BukkitAudiences audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
@Getter
private final Player bukkitPlayer;
@Getter
private final PlatformInventory inventory;
private final @Nullable User user;
public BukkitPlatformPlayer(Player bukkitPlayer) {
super(bukkitPlayer);
this.bukkitPlayer = bukkitPlayer;
this.inventory = new BukkitPlatformInventory(bukkitPlayer);
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(bukkitPlayer.getUniqueId());
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
} else {
this.user = null;
}
}
@Override
@@ -77,21 +64,12 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Override
public void sendMessage(String message) {
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
user.sendMessage(message);
} else {
bukkitPlayer.sendMessage(message);
}
bukkitPlayer.sendMessage(message);
}
@Override
public void sendMessage(Component message) {
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);
}
@Override
@@ -111,27 +89,28 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Override
public Vector3d getPosition() {
if (CAN_USE_DIRECT_GETTERS) {
return new Vector3d(this.bukkitPlayer.getX(), this.bukkitPlayer.getY(), this.bukkitPlayer.getZ());
} else {
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
}
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
}
@Override
public @Nullable GrimEntity getVehicle() {
return bukkitPlayer.getVehicle() == null ? null : new BukkitGrimEntity(bukkitPlayer.getVehicle());
public PlatformInventory getInventory() {
return inventory;
}
@Override
public GrimEntity getVehicle() {
return new BukkitGrimEntity(bukkitPlayer.getVehicle());
}
@Override
public GameMode getGameMode() {
return SpigotConversionUtil.fromBukkitGameMode(bukkitPlayer.getGameMode());
return BukkitConversionUtils.fromBukkitGameMode(bukkitPlayer.getGameMode());
}
@Override
public void setGameMode(GameMode gameMode) {
bukkitPlayer.setGameMode(SpigotConversionUtil.toBukkitGameMode(gameMode));
bukkitPlayer.setGameMode(BukkitConversionUtils.toBukkitGameMode(gameMode));
}
public World getBukkitWorld() {
@@ -170,12 +149,7 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
}
@Override
public BlockTranslator getBlockTranslator() {
return BlockTranslator.IDENTITY;
}
@Override
@NotNull
@NonNull
public Player getNative() {
return this.bukkitPlayer;
}
@@ -1,40 +1,41 @@
package ac.grim.grimac.platform.bukkit.player;
import ac.grim.grimac.platform.api.player.AbstractPlatformPlayerFactory;
import ac.grim.grimac.platform.api.player.OfflinePlatformPlayer;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
import org.bukkit.Bukkit;
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;
public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory<Player> {
@Override
protected Player getNativePlayer(@NotNull UUID uuid) {
return Bukkit.getPlayer(uuid);
}
@Override
protected Player getNativePlayer(@NotNull String name) {
return Bukkit.getPlayer(name);
}
@Override
protected PlatformPlayer createPlatformPlayer(@NotNull Player nativePlayer) {
return new BukkitPlatformPlayer(nativePlayer);
}
@Override
protected boolean isNativePlayerType(@NotNull Object playerObject) {
return playerObject instanceof Player;
}
@Override
protected UUID getPlayerUUID(@NotNull Player nativePlayer) {
return nativePlayer.getUniqueId();
}
@Override
protected Class<Player> getNativePlayerClass() {
return Player.class;
}
// The cast is safe because Bukkit.getOnlinePlayers() is guaranteed to contain Player or its subtypes,
// and we're only reading from it.
@Override
@@ -43,26 +44,4 @@ public class BukkitPlatformPlayerFactory extends AbstractPlatformPlayerFactory<P
// Cast Collection<? extends Player> to Collection<Player>
return (Collection<Player>) Bukkit.getOnlinePlayers();
}
@Override
public OfflinePlatformPlayer getOfflineFromUUID(@NotNull UUID uuid) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
return new BukkitOfflinePlatformPlayer(offlinePlayer);
}
@Override
public OfflinePlatformPlayer getOfflineFromName(@NotNull String name) {
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);
}
}
@@ -22,31 +22,29 @@ public class BukkitAsyncScheduler implements AsyncScheduler {
@Override
public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull Runnable task, long delay, @NotNull TimeUnit timeUnit) {
return new BukkitTaskHandle(bukkitScheduler.runTaskLaterAsynchronously(
GrimACBukkitLoaderPlugin.LOADER,
return new BukkitTaskHandle(bukkitScheduler.runTaskLaterAsynchronously(GrimACBukkitLoaderPlugin.LOADER,
task,
PlatformScheduler.convertTimeToTicks(delay, timeUnit)
));
PlatformScheduler.convertTimeToTicks(delay, timeUnit))
);
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable task, long delay, long period, @NotNull TimeUnit timeUnit) {
return new BukkitTaskHandle(bukkitScheduler.runTaskTimerAsynchronously(
GrimACBukkitLoaderPlugin.LOADER,
task,
PlatformScheduler.convertTimeToTicks(delay, timeUnit),
PlatformScheduler.convertTimeToTicks(period, timeUnit)
));
return new BukkitTaskHandle(
bukkitScheduler.runTaskTimerAsynchronously(
GrimACBukkitLoaderPlugin.LOADER,
task,
PlatformScheduler.convertTimeToTicks(delay, timeUnit),
PlatformScheduler.convertTimeToTicks(period, timeUnit)
)
);
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable task, long initialDelayTicks, long periodTicks) {
return new BukkitTaskHandle(bukkitScheduler.runTaskTimerAsynchronously(
GrimACBukkitLoaderPlugin.LOADER,
task,
initialDelayTicks,
periodTicks
));
return new BukkitTaskHandle(
bukkitScheduler.runTaskTimerAsynchronously(GrimACBukkitLoaderPlugin.LOADER, task, initialDelayTicks, periodTicks)
);
}
@Override
@@ -11,25 +11,26 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class BukkitEntityScheduler implements EntityScheduler {
private final BukkitScheduler scheduler = Bukkit.getScheduler();
private final BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
@Override
public void execute(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay) {
scheduler.runTaskLater(GrimACBukkitLoaderPlugin.LOADER, run, delay);
bukkitScheduler.runTaskLater(GrimACBukkitLoaderPlugin.LOADER, run, delay);
}
@Override
public TaskHandle run(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired) {
return new BukkitTaskHandle(scheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, task));
return new BukkitTaskHandle(bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, task));
}
@Override
public TaskHandle runDelayed(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired, long delayTicks) {
return new BukkitTaskHandle(scheduler.runTaskLater(GrimACBukkitLoaderPlugin.LOADER, task, delayTicks));
return new BukkitTaskHandle(bukkitScheduler.runTaskLater(GrimACBukkitLoaderPlugin.LOADER, task, delayTicks));
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired, long initialDelayTicks, long periodTicks) {
return new BukkitTaskHandle(scheduler.runTaskTimer(GrimACBukkitLoaderPlugin.LOADER, task, initialDelayTicks, periodTicks));
return new BukkitTaskHandle(bukkitScheduler.runTaskTimer(GrimACBukkitLoaderPlugin.LOADER, task, initialDelayTicks, periodTicks));
}
}
@@ -13,8 +13,8 @@ public class BukkitGlobalRegionScheduler implements GlobalRegionScheduler {
private final BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull Runnable task) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull Runnable run) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, run);
}
@Override
@@ -1,13 +1,37 @@
package ac.grim.grimac.platform.bukkit.scheduler.bukkit;
import ac.grim.grimac.platform.api.scheduler.AsyncScheduler;
import ac.grim.grimac.platform.api.scheduler.EntityScheduler;
import ac.grim.grimac.platform.api.scheduler.GlobalRegionScheduler;
import ac.grim.grimac.platform.api.scheduler.PlatformScheduler;
import lombok.Getter;
import ac.grim.grimac.platform.api.scheduler.RegionScheduler;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
@Getter
public class BukkitPlatformScheduler implements PlatformScheduler {
private final @NotNull BukkitAsyncScheduler asyncScheduler = new BukkitAsyncScheduler();
private final @NotNull BukkitGlobalRegionScheduler globalRegionScheduler = new BukkitGlobalRegionScheduler();
private final @NotNull BukkitEntityScheduler entityScheduler = new BukkitEntityScheduler();
private final @NotNull BukkitRegionScheduler regionScheduler = new BukkitRegionScheduler();
private final BukkitAsyncScheduler bukkitAsyncScheduler = new BukkitAsyncScheduler();
private final BukkitGlobalRegionScheduler bukkitGlobalRegionScheduler = new BukkitGlobalRegionScheduler();
private final BukkitEntityScheduler bukkitEntityScheduler = new BukkitEntityScheduler();
private final BukkitRegionScheduler bukkitRegionScheduler = new BukkitRegionScheduler();
@Override
public @NonNull @NotNull AsyncScheduler getAsyncScheduler() {
return bukkitAsyncScheduler;
}
@Override
public @NonNull @NotNull GlobalRegionScheduler getGlobalRegionScheduler() {
return bukkitGlobalRegionScheduler;
}
@Override
public @NonNull @NotNull EntityScheduler getEntityScheduler() {
return bukkitEntityScheduler;
}
@Override
public @NonNull @NotNull RegionScheduler getRegionScheduler() {
return bukkitRegionScheduler;
}
}
@@ -15,13 +15,13 @@ public class BukkitRegionScheduler implements RegionScheduler {
private final BukkitScheduler bukkitScheduler = Bukkit.getScheduler();
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable task) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable run) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, run);
}
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull Location location, @NotNull Runnable task) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull Location location, @NotNull Runnable run) {
bukkitScheduler.runTask(GrimACBukkitLoaderPlugin.LOADER, run);
}
@Override
@@ -2,32 +2,27 @@ package ac.grim.grimac.platform.bukkit.scheduler.bukkit;
import ac.grim.grimac.platform.api.scheduler.TaskHandle;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class BukkitTaskHandle implements TaskHandle {
private final @NotNull BukkitTask task;
private final BukkitTask bukkitTask;
@Contract(pure = true)
public BukkitTaskHandle(@NotNull BukkitTask task) {
this.task = Objects.requireNonNull(task);
public BukkitTaskHandle(BukkitTask bukkitTask) {
this.bukkitTask = bukkitTask;
}
@Override
public boolean isSync() {
return task.isSync();
return bukkitTask.isSync();
}
@Override
public boolean isCancelled() {
return task.isCancelled();
public boolean getCancelled() {
return bukkitTask.isCancelled();
}
@Override
public void cancel() {
task.cancel();
bukkitTask.cancel();
}
}
@@ -11,47 +11,53 @@ import java.util.concurrent.TimeUnit;
public class FoliaAsyncScheduler implements AsyncScheduler {
private final io.papermc.paper.threadedregions.scheduler.AsyncScheduler scheduler = Bukkit.getAsyncScheduler();
private final io.papermc.paper.threadedregions.scheduler.AsyncScheduler asyncScheduler = Bukkit.getAsyncScheduler();
@Override
public TaskHandle runNow(@NotNull GrimPlugin plugin, @NotNull Runnable task) {
return new FoliaTaskHandle(scheduler.runNow(GrimACBukkitLoaderPlugin.LOADER, ignored -> task.run()));
return new FoliaTaskHandle(asyncScheduler.runNow(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run()));
}
@Override
public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull Runnable task, long delay, @NotNull TimeUnit timeUnit) {
return new FoliaTaskHandle(scheduler.runDelayed(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
delay,
timeUnit
));
return new FoliaTaskHandle(
asyncScheduler.runDelayed(
GrimACBukkitLoaderPlugin.LOADER,
(ignored) -> task.run(),
delay,
timeUnit
)
);
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable task, long delay, long period, @NotNull TimeUnit timeUnit) {
return new FoliaTaskHandle(scheduler.runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
delay,
period,
timeUnit
));
return new FoliaTaskHandle(
asyncScheduler.runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
(ignored) -> task.run(),
delay,
period,
timeUnit
)
);
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable task, long initialDelayTicks, long periodTicks) {
return new FoliaTaskHandle(scheduler.runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
initialDelayTicks * 50,
periodTicks * 50,
TimeUnit.MILLISECONDS
));
return new FoliaTaskHandle(
asyncScheduler.runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
(ignored) -> task.run(),
initialDelayTicks * 50,
periodTicks * 50,
TimeUnit.MILLISECONDS
)
);
}
@Override
public void cancel(@NotNull GrimPlugin plugin) {
scheduler.cancelTasks(GrimACBukkitLoaderPlugin.LOADER);
asyncScheduler.cancelTasks(GrimACBukkitLoaderPlugin.LOADER);
}
}
@@ -6,50 +6,38 @@ import ac.grim.grimac.platform.api.scheduler.EntityScheduler;
import ac.grim.grimac.platform.api.scheduler.TaskHandle;
import ac.grim.grimac.platform.bukkit.GrimACBukkitLoaderPlugin;
import ac.grim.grimac.platform.bukkit.entity.BukkitGrimEntity;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FoliaEntityScheduler implements EntityScheduler {
@Override
public void execute(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired, long delay) {
((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().execute(GrimACBukkitLoaderPlugin.LOADER, task, retired, delay);
public void execute(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay) {
((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().execute(GrimACBukkitLoaderPlugin.LOADER, run, () -> {
}, delay);
}
@Override
public TaskHandle run(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired) {
ScheduledTask scheduled = ((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().run(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
retired
return new FoliaTaskHandle(
((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().run(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run(), () -> {
})
);
return scheduled == null ? null : new FoliaTaskHandle(scheduled);
}
@Override
public TaskHandle runDelayed(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired, long delayTicks) {
ScheduledTask scheduled = ((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().runDelayed(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
retired,
delayTicks
return new FoliaTaskHandle(
((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().runDelayed(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run(), () -> {
}, delayTicks)
);
return scheduled == null ? null : new FoliaTaskHandle(scheduled);
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimEntity entity, @NotNull GrimPlugin plugin, @NotNull Runnable task, @Nullable Runnable retired, long initialDelayTicks, long periodTicks) {
ScheduledTask scheduled = ((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
ignored -> task.run(),
retired,
initialDelayTicks,
periodTicks
return new FoliaTaskHandle(
((BukkitGrimEntity) entity).getBukkitEntity().getScheduler().runAtFixedRate(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run(), () -> {
}, initialDelayTicks, periodTicks)
);
return scheduled == null ? null : new FoliaTaskHandle(scheduled);
}
}
@@ -12,23 +12,23 @@ public class FoliaGlobalRegionScheduler implements GlobalRegionScheduler {
private final io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler globalRegionScheduler = Bukkit.getGlobalRegionScheduler();
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull Runnable task) {
globalRegionScheduler.execute(GrimACBukkitLoaderPlugin.LOADER, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull Runnable run) {
globalRegionScheduler.execute(GrimACBukkitLoaderPlugin.LOADER, run);
}
@Override
public TaskHandle run(@NotNull GrimPlugin plugin, @NotNull Runnable task) {
return new FoliaTaskHandle(globalRegionScheduler.run(GrimACBukkitLoaderPlugin.LOADER, ignored -> task.run()));
return new FoliaTaskHandle(globalRegionScheduler.run(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run()));
}
@Override
public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull Runnable task, long delay) {
return new FoliaTaskHandle(globalRegionScheduler.runDelayed(GrimACBukkitLoaderPlugin.LOADER, ignored -> task.run(), delay));
return new FoliaTaskHandle(globalRegionScheduler.runDelayed(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run(), delay));
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Runnable task, long initialDelayTicks, long periodTicks) {
return new FoliaTaskHandle(globalRegionScheduler.runAtFixedRate(GrimACBukkitLoaderPlugin.LOADER, ignored -> task.run(), initialDelayTicks, periodTicks));
return new FoliaTaskHandle(globalRegionScheduler.runAtFixedRate(GrimACBukkitLoaderPlugin.LOADER, (ignored) -> task.run(), initialDelayTicks, periodTicks));
}
@Override
@@ -1,13 +1,36 @@
package ac.grim.grimac.platform.bukkit.scheduler.folia;
import ac.grim.grimac.platform.api.scheduler.AsyncScheduler;
import ac.grim.grimac.platform.api.scheduler.EntityScheduler;
import ac.grim.grimac.platform.api.scheduler.GlobalRegionScheduler;
import ac.grim.grimac.platform.api.scheduler.PlatformScheduler;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import ac.grim.grimac.platform.api.scheduler.RegionScheduler;
import org.checkerframework.checker.nullness.qual.NonNull;
@Getter
public class FoliaPlatformScheduler implements PlatformScheduler {
private final @NotNull FoliaAsyncScheduler asyncScheduler = new FoliaAsyncScheduler();
private final @NotNull FoliaGlobalRegionScheduler globalRegionScheduler = new FoliaGlobalRegionScheduler();
private final @NotNull FoliaEntityScheduler entityScheduler = new FoliaEntityScheduler();
private final @NotNull FoliaRegionScheduler regionScheduler = new FoliaRegionScheduler();
private final FoliaAsyncScheduler foliaAsyncScheduler = new FoliaAsyncScheduler();
private final FoliaGlobalRegionScheduler foliaGlobalRegionScheduler = new FoliaGlobalRegionScheduler();
private final FoliaEntityScheduler foliaEntityScheduler = new FoliaEntityScheduler();
private final FoliaRegionScheduler foliaRegionScheduler = new FoliaRegionScheduler();
@Override
public @NonNull AsyncScheduler getAsyncScheduler() {
return foliaAsyncScheduler;
}
@Override
public @NonNull GlobalRegionScheduler getGlobalRegionScheduler() {
return foliaGlobalRegionScheduler;
}
@Override
public @NonNull EntityScheduler getEntityScheduler() {
return foliaEntityScheduler;
}
@Override
public @NonNull RegionScheduler getRegionScheduler() {
return foliaRegionScheduler;
}
}
@@ -15,24 +15,19 @@ public class FoliaRegionScheduler implements RegionScheduler {
private final io.papermc.paper.threadedregions.scheduler.RegionScheduler regionScheduler = Bukkit.getRegionScheduler();
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable task) {
regionScheduler.execute(GrimACBukkitLoaderPlugin.LOADER, ((BukkitPlatformWorld) world).getBukkitWorld(), chunkX, chunkZ, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable run) {
regionScheduler.execute(GrimACBukkitLoaderPlugin.LOADER, ((BukkitPlatformWorld) world).getBukkitWorld(), chunkX, chunkZ, run);
}
@Override
public void execute(@NotNull GrimPlugin plugin, @NotNull Location location, @NotNull Runnable task) {
execute(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task);
public void execute(@NotNull GrimPlugin plugin, @NotNull Location location, @NotNull Runnable run) {
execute(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, run);
}
@Override
public TaskHandle run(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable task) {
return new FoliaTaskHandle(regionScheduler.run(
GrimACBukkitLoaderPlugin.LOADER,
((BukkitPlatformWorld) world).getBukkitWorld(),
chunkX,
chunkZ,
ignored -> task.run()
));
return new FoliaTaskHandle(regionScheduler.run(GrimACBukkitLoaderPlugin.LOADER, ((BukkitPlatformWorld) world).getBukkitWorld(), chunkX, chunkZ, (ignored) -> task.run()));
}
@Override
@@ -42,14 +37,8 @@ public class FoliaRegionScheduler implements RegionScheduler {
@Override
public TaskHandle runDelayed(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable task, long delayTicks) {
return new FoliaTaskHandle(regionScheduler.runDelayed(
GrimACBukkitLoaderPlugin.LOADER,
((BukkitPlatformWorld) world).getBukkitWorld(),
chunkX,
chunkZ,
ignored -> task.run(),
delayTicks
));
return new FoliaTaskHandle(regionScheduler.runDelayed(GrimACBukkitLoaderPlugin.LOADER,
((BukkitPlatformWorld) world).getBukkitWorld(), chunkX, chunkZ, (ignored) -> task.run(), delayTicks));
}
@Override
@@ -59,27 +48,12 @@ public class FoliaRegionScheduler implements RegionScheduler {
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull PlatformWorld world, int chunkX, int chunkZ, @NotNull Runnable task, long initialDelayTicks, long periodTicks) {
return new FoliaTaskHandle(regionScheduler.runAtFixedRate(
GrimACBukkitLoaderPlugin.LOADER,
((BukkitPlatformWorld) world).getBukkitWorld(),
chunkX,
chunkZ,
ignored -> task.run(),
initialDelayTicks,
periodTicks
));
return new FoliaTaskHandle(regionScheduler.runAtFixedRate(GrimACBukkitLoaderPlugin.LOADER,
((BukkitPlatformWorld) world).getBukkitWorld(), chunkX, chunkZ, (ignored) -> task.run(), initialDelayTicks, periodTicks));
}
@Override
public TaskHandle runAtFixedRate(@NotNull GrimPlugin plugin, @NotNull Location location, @NotNull Runnable task, long initialDelayTicks, long periodTicks) {
return runAtFixedRate(
plugin,
location.getWorld(),
location.getBlockX() >> 4,
location.getBlockZ() >> 4,
task,
initialDelayTicks,
periodTicks
);
return runAtFixedRate(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, initialDelayTicks, periodTicks);
}
}
@@ -2,18 +2,13 @@ package ac.grim.grimac.platform.bukkit.scheduler.folia;
import ac.grim.grimac.platform.api.scheduler.TaskHandle;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class FoliaTaskHandle implements TaskHandle {
private final @NotNull ScheduledTask task;
private final ScheduledTask task;
@Contract(pure = true)
public FoliaTaskHandle(@NotNull ScheduledTask task) {
this.task = Objects.requireNonNull(task);
public FoliaTaskHandle(ScheduledTask task) {
this.task = task;
}
@Override
@@ -22,7 +17,7 @@ public class FoliaTaskHandle implements TaskHandle {
}
@Override
public boolean isCancelled() {
public boolean getCancelled() {
return task.isCancelled();
}
@@ -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;
@@ -10,12 +12,17 @@ import org.bukkit.command.RemoteConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.SenderMapper;
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) {
@@ -24,7 +31,7 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> implements
@Override
protected UUID getUniqueId(CommandSender sender) {
return sender instanceof Player player ? player.getUniqueId() : Sender.CONSOLE_UUID;
return sender instanceof Player ? ((Player) sender).getUniqueId() : Sender.CONSOLE_UUID;
}
@Override
@@ -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)
);
}
}
@@ -72,12 +78,12 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> implements
}
@Override
public @NotNull Sender map(@NotNull CommandSender base) {
public @NonNull Sender map(@NonNull CommandSender base) {
return this.wrap(base);
}
@Override
public @NotNull CommandSender reverse(@NotNull Sender mapped) {
public @NonNull CommandSender reverse(@NonNull Sender mapped) {
return this.unwrap(mapped);
}
}
@@ -1,7 +1,5 @@
package ac.grim.grimac.platform.bukkit.utils.anticheat;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.reflection.ReflectionUtils;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import org.bukkit.entity.Player;
@@ -10,8 +8,17 @@ import java.lang.reflect.Method;
public class MultiLibUtil {
public final static Method externalPlayerMethod = ReflectionUtils.getMethod(Player.class, "isExternalPlayer");
private static final boolean IS_PRE_1_18 = PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_18);
public final static Method externalPlayerMethod = getMethod(Player.class, "isExternalPlayer");
private static final boolean
IS_PRE_1_18 = PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_18);
public static Method getMethod(Class<?> clazz, String methodName) {
try {
return clazz.getMethod(methodName);
} catch (NoSuchMethodException e) {
return null;
}
}
// TODO: cache external players for better performance, but this only matters for people using multi-lib
public static boolean isExternalPlayer(Player player) {
@@ -19,8 +26,10 @@ public class MultiLibUtil {
try {
return (boolean) externalPlayerMethod.invoke(player);
} catch (Exception e) {
LogUtil.error("Failed to invoke external player method", e);
e.printStackTrace();
return false;
}
}
}
@@ -0,0 +1,22 @@
package ac.grim.grimac.platform.bukkit.utils.blockstate;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
public class BukkitBlockFaceHelper {
/**
* For use with bukkit events only
* Grim is not meant to be restrained by bukkit!
*/
@Deprecated
public static BlockFace fromBukkitFace(org.bukkit.block.BlockFace face) {
return switch (face) {
case NORTH -> BlockFace.NORTH;
case SOUTH -> BlockFace.SOUTH;
case WEST -> BlockFace.WEST;
case EAST -> BlockFace.EAST;
case UP -> BlockFace.UP;
case DOWN -> BlockFace.DOWN;
default -> BlockFace.OTHER;
};
}
}
@@ -3,43 +3,60 @@ package ac.grim.grimac.platform.bukkit.utils.convert;
import ac.grim.grimac.platform.api.permissions.PermissionDefaultValue;
import ac.grim.grimac.platform.bukkit.world.BukkitPlatformWorld;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import com.github.retrooper.packetevents.protocol.player.GameMode;
public class BukkitConversionUtils {
@Contract("null -> null; !null -> new")
public static org.bukkit.Location toBukkitLocation(Location location) {
if (location == null) return null;
return new org.bukkit.Location(((BukkitPlatformWorld) location.getWorld()).getBukkitWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
}
public static GameMode fromBukkitGameMode(org.bukkit.GameMode gameMode) {
switch (gameMode) {
case CREATIVE:
return GameMode.CREATIVE;
case SURVIVAL:
return GameMode.SURVIVAL;
case ADVENTURE:
return GameMode.ADVENTURE;
case SPECTATOR:
return GameMode.SPECTATOR;
default:
throw new IllegalStateException();
}
}
public static org.bukkit.GameMode toBukkitGameMode(GameMode gameMode) {
switch (gameMode) {
case CREATIVE:
return org.bukkit.GameMode.CREATIVE;
case SURVIVAL:
return org.bukkit.GameMode.SURVIVAL;
case ADVENTURE:
return org.bukkit.GameMode.ADVENTURE;
case SPECTATOR:
return org.bukkit.GameMode.SPECTATOR;
default:
throw new IllegalStateException();
}
}
/**
* Converts this enum to a Bukkit PermissionDefault.
*
* @return The corresponding Bukkit PermissionDefault.
*/
@Contract(value = "null -> null; !null -> !null", pure = true)
public static @Nullable PermissionDefault toBukkitPermissionDefault(@Nullable PermissionDefaultValue permissionDefaultValue) {
if (permissionDefaultValue == null) return null;
return switch (permissionDefaultValue) {
case TRUE -> PermissionDefault.TRUE;
case FALSE -> PermissionDefault.FALSE;
case OP -> PermissionDefault.OP;
case NOT_OP -> PermissionDefault.NOT_OP;
};
}
public static BlockFace fromBukkitFace(org.bukkit.block.BlockFace face) {
return switch (face) {
case NORTH -> BlockFace.NORTH;
case SOUTH -> BlockFace.SOUTH;
case WEST -> BlockFace.WEST;
case EAST -> BlockFace.EAST;
case UP -> BlockFace.UP;
case DOWN -> BlockFace.DOWN;
default -> BlockFace.OTHER;
};
public static org.bukkit.permissions.PermissionDefault toBukkitPermissionDefault(PermissionDefaultValue permissionDefaultValue) {
switch (permissionDefaultValue) {
case TRUE:
return org.bukkit.permissions.PermissionDefault.TRUE;
case FALSE:
return org.bukkit.permissions.PermissionDefault.FALSE;
case OP:
return org.bukkit.permissions.PermissionDefault.OP;
case NOT_OP:
return org.bukkit.permissions.PermissionDefault.NOT_OP;
default:
throw new IllegalStateException();
}
}
}
@@ -8,7 +8,6 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -37,16 +36,9 @@ public class PlaceholderAPIExpansion extends PlaceholderExpansion {
@Override
public @NotNull List<String> getPlaceholders() {
Set<String> staticReplacements = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().keySet();
Set<String> variableReplacements = GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().keySet();
ArrayList<String> placeholders = new ArrayList<>(staticReplacements.size() + variableReplacements.size());
for (String s : staticReplacements) {
placeholders.add(s.equals("%grim_version%") ? s : "%grim_" + s.replaceAll("%", "") + "%");
}
for (String s : variableReplacements) {
placeholders.add(s.equals("%player%") ? "%grim_player%" : "%grim_player_" + s.replaceAll("%", "") + "%");
}
return placeholders;
Set<String> placeholders = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().keySet();
placeholders.addAll(GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().keySet());
return List.copyOf(placeholders);
}
@Override
@@ -12,29 +12,41 @@ import org.bukkit.event.Listener;
import java.util.concurrent.CompletableFuture;
public class PaperUtils {
public static final boolean PAPER = ReflectionUtils.hasClass("com.destroystokyo.paper.PaperConfig")
|| ReflectionUtils.hasClass("io.papermc.paper.configuration.Configuration");
public static final boolean PAPER;
public static final boolean TICK_END_EVENT;
public static final boolean ASYNC_TELEPORT;
static {
PAPER = ReflectionUtils.hasClass("com.destroystokyo.paper.PaperConfig") || ReflectionUtils.hasClass("io.papermc.paper.configuration.Configuration");
TICK_END_EVENT = ReflectionUtils.hasClass("com.destroystokyo.paper.event.server.ServerTickEndEvent");
ASYNC_TELEPORT = ReflectionUtils.hasMethod(Entity.class, "teleportAsync", org.bukkit.Location.class);
}
public static CompletableFuture<Boolean> teleportAsync(final Entity entity, final Location location) {
return PAPER ? entity.teleportAsync(location) : CompletableFuture.completedFuture(entity.teleport(location));
if (PAPER) {
return entity.teleportAsync(location);
} else {
return CompletableFuture.completedFuture(entity.teleport(location));
}
}
@SuppressWarnings("unchecked")
public static boolean registerTickEndEvent(Listener listener, Runnable runnable) {
try {
Class<?> clazz = ReflectionUtils.getClass("com.destroystokyo.paper.event.server.ServerTickEndEvent");
if (clazz == null) return false;
GrimACBukkitLoaderPlugin.LOADER.getServer().getPluginManager().registerEvent(
(Class<? extends Event>) clazz,
listener,
EventPriority.NORMAL,
(l, event) -> runnable.run(),
GrimACBukkitLoaderPlugin.LOADER
);
return true;
} catch (Exception e) {
LogUtil.error("Failed to register tick end event", e);
if (TICK_END_EVENT) {
try {
Class<?> clazz = ReflectionUtils.getClass("com.destroystokyo.paper.event.server.ServerTickEndEvent");
if (clazz == null) return false;
GrimACBukkitLoaderPlugin.LOADER.getServer().getPluginManager().registerEvent((Class<? extends Event>) clazz,
listener,
EventPriority.NORMAL,
(l, event) -> runnable.run(), GrimACBukkitLoaderPlugin.LOADER);
return true;
} catch (Exception e) {
LogUtil.exception("Failed to register tick end event", e);
}
}
return false;
}
}
@@ -1,26 +1,24 @@
package ac.grim.grimac.platform.bukkit.world;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.Platform;
import ac.grim.grimac.platform.api.world.PlatformChunk;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import lombok.RequiredArgsConstructor;
import org.bukkit.Chunk;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RequiredArgsConstructor
public class BukkitPlatformChunk implements PlatformChunk {
private static final Map<BlockData, Integer> blockDataToId = GrimAPI.INSTANCE.getPlatform() == Platform.FOLIA ? new ConcurrentHashMap<>() : new HashMap<>();
private static final HashMap<BlockData, Integer> blockDataToId = new HashMap<>();
private static final boolean isFlat = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13);
private final @NotNull Chunk chunk;
private final Chunk chunk;
public BukkitPlatformChunk(@NotNull Chunk chunkAt) {
this.chunk = chunkAt;
}
@Override
public int getBlockID(int x, int y, int z) {
@@ -28,11 +26,6 @@ public class BukkitPlatformChunk implements PlatformChunk {
return isFlat // Cache blockDataToID because Strings are expensive
? blockDataToId.computeIfAbsent(block.getBlockData(), data -> WrappedBlockState.getByString(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), data.getAsString(false)).getGlobalId())
: getLegacyBlockID(block);
}
@SuppressWarnings({ "deprecation", "UnstableApiUsage" })
private static int getLegacyBlockID(@NotNull Block block) {
return (block.getType().getId() << 4) | block.getData();
: (block.getType().getId() << 4) | block.getData();
}
}
@@ -2,26 +2,24 @@ package ac.grim.grimac.platform.bukkit.world;
import ac.grim.grimac.platform.api.world.PlatformChunk;
import ac.grim.grimac.platform.api.world.PlatformWorld;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@Getter
@RequiredArgsConstructor
public class BukkitPlatformWorld implements PlatformWorld {
private static final boolean LEGACY_SERVER_VERSION = PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_12_2);
private final @NotNull World bukkitWorld;
private final World bukkitWorld;
public BukkitPlatformWorld(@NotNull World world) {
this.bukkitWorld = world;
}
@Override
public boolean isChunkLoaded(int chunkX, int chunkZ) {
@@ -30,14 +28,7 @@ public class BukkitPlatformWorld implements PlatformWorld {
@Override
public WrappedBlockState getBlockAt(int x, int y, int z) {
if (LEGACY_SERVER_VERSION) {
Block block = bukkitWorld.getBlockAt(x, y, z);
@SuppressWarnings({"deprecation", "UnstableApiUsage"})
int blockId = (block.getType().getId() << 4) | block.getData();
return WrappedBlockState.getByGlobalId(blockId);
} else {
return SpigotConversionUtil.fromBukkitBlockData(bukkitWorld.getBlockAt(x, y, z).getBlockData());
}
return SpigotConversionUtil.fromBukkitBlockData(bukkitWorld.getBlockAt(x, y, z).getBlockData());
}
@Override
-47
View File
@@ -1,47 +0,0 @@
# cliff.toml - DEV BUILDS - Now with Author Info
[changelog]
body = """
## Changes in this Build (since previous push)
{%- for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | upper_first }}
{%- for commit in commits %}
- {{ commit.message | upper_first }} - *by {{ commit.author.name }}* {% if commit.breaking %}**[BREAKING]**{% endif %} \
{%- if commit.remote and commit.remote.url -%}
([`{{ commit.id | truncate(length=7, end="") }}`]({{ commit.remote.url }}/commit/{{- commit.id -}}))
{%- else -%}
(`{{ commit.id | truncate(length=7, end="") }}`)
{%- endif -%}
{% endfor %}
{% endfor %}
"""
trim = true
[git]
# Your previous settings here are perfect, no changes needed
filter_unconventional = false
skip_unconventional = false
remote = "https://github.com/GrimAnticheat/Grim"
commit_parsers = [
{ message = "^feat", group = "🚀 Features" },
{ message = "^fix", group = "🐛 Fixes" },
{ message = "^perf", group = "⚡️ Performance" },
{ message = "^refactor", group = "♻️ Refactoring" },
{ message = "^docs", group = "📖 Documentation" },
{ message = "^test", group = "✅ Tests" },
{ message = "^chore", group = "🤖 Maintenance & CI" },
{ body = ".*BREAKING CHANGE.*", group = "🚨 Breaking Changes" },
{ message = ".*", group = "Miscellaneous" } # CHANGED THIS LINE
]
commit_preprocessors = [
{ pattern = 'src/main/java/ac/grim/grimac/checks/.*', group = "✔️ Checks" },
{ pattern = 'src/main/java/ac/grim/grimac/api/.*', group = "🔌 API" },
{ pattern = 'src/main/java/ac/grim/grimac/platform/.*', group = "🏗️ Platform" },
{ pattern = 'src/main/java/ac/grim/grimac/engine/.*', group = "⚙️ Core Engine" },
{ pattern = 'src/main/java/ac/grim/grimac/config/.*', group = "⚙️ Configuration" },
{ pattern = 'pom.xml', group = "🤖 Maintenance & CI" },
{ pattern = '.github/.*', group = "🤖 Maintenance & CI" },
{ pattern = 'build.gradle.kts', group = "🤖 Maintenance & CI" },
{ pattern = 'src/.*', group = "⚙️ Core Engine" },
]
sort_commits = "oldest"
+28 -62
View File
@@ -1,85 +1,51 @@
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")
mavenLocal()
maven {
name = "papermc"
url = uri("https://repo.papermc.io/repository/maven-public/")
}
// PacketEvents
exclusive("https://repo.codemc.io/repository/maven-releases/", { mavenContent { releasesOnly() } }) {
includeGroup("com.github.retrooper")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // Spigot
maven("https://jitpack.io/") { // Grim API
content {
includeGroup("com.github.grimanticheat")
}
}
// ViaVersion
exclusive("https://repo.viaversion.com", { mavenContent { releasesOnly() } }) {
includeGroup("com.viaversion")
}
// Configuralize
exclusive("https://nexus.scarsz.me/content/repositories/releases", { mavenContent { releasesOnly() } }) {
includeGroup("github.scarsz")
}
// Cumulus
exclusive("https://repo.opencollab.dev/maven-releases/", { mavenContent { releasesOnly() } }) {
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")
}
maven("https://repo.viaversion.com") // ViaVersion
maven("https://nexus.scarsz.me/content/repositories/releases") // Configuralize
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.opencollab.dev/maven-releases/") // Cumulus (for Floodgate)
maven("https://repo.codemc.io/repository/maven-releases/") // PacketEvents
maven("https://repo.codemc.io/repository/maven-snapshots/")
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
mavenCentral()
// FastUtil, Discord-Webhooks
}
dependencies {
if (BuildConfig.shadePE) {
api(libs.packetevents.api)
} else {
compileOnly(libs.packetevents.api)
}
api(libs.packetevents.api)
api(libs.cloud.core)
api(libs.cloud.processors.requirements)
api(libs.configuralize) {
artifact {
classifier = "slim"
}
api("org.incendo:cloud-processors-requirements:1.0.0-rc.1")
api("github.scarsz:configuralize:1.4.1:slim") {
exclude(group = "org.yaml", module = "snakeyaml")
}
// Bump snakeyaml (transitive dep of configuralize) 1.29 -> 2.2+ for geyser-fabric
api(libs.snakeyaml)
// Bump snakeyaml (transitive dep of configuralize) 1.29 -> 2.2 for geyser-fabric
api("org.yaml:snakeyaml:2.2")
api(libs.discord.webhooks)
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
}
// Used for local testing:
// api("ac.grim.grimac:GrimAPI:1.0")
api("com.github.grimanticheat:grimapi:8507e14a73")
compileOnly(libs.floodgate.api)
compileOnly(libs.viaversion)
compileOnly(libs.via.version.api)
compileOnly(libs.netty)
}
@@ -1,9 +1,6 @@
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.manager.AlertManagerImpl;
import ac.grim.grimac.manager.DiscordManager;
import ac.grim.grimac.manager.InitManager;
@@ -11,29 +8,30 @@ import ac.grim.grimac.manager.SpectateManager;
import ac.grim.grimac.manager.TickManager;
import ac.grim.grimac.manager.config.BaseConfigManager;
import ac.grim.grimac.manager.init.Initable;
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.ParserDescriptorFactory;
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.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.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Getter
public final class GrimAPI {
public static final GrimAPI INSTANCE = new GrimAPI();
@Getter
private final Platform platform = detectPlatform();
private final BaseConfigManager configManager;
private final AlertManagerImpl alertManager;
@@ -41,12 +39,8 @@ public final class GrimAPI {
private final DiscordManager discordManager;
private final PlayerDataManager playerDataManager;
private final TickManager tickManager;
private final GrimExtensionManager extensionManager;
private final EventBus eventBus;
private final GrimExternalAPI externalAPI;
private ViolationDatabaseManager violationDatabaseManager;
private PlatformLoader loader;
@Getter
private InitManager initManager;
private boolean initialized = false;
@@ -57,25 +51,26 @@ public final class GrimAPI {
this.discordManager = new DiscordManager();
this.playerDataManager = new PlayerDataManager();
this.tickManager = new TickManager();
this.extensionManager = new GrimExtensionManager();
this.eventBus = new OptimizedEventBus(extensionManager);
this.externalAPI = new GrimExternalAPI(this);
}
// the order matters
private static Platform detectPlatform() {
Platform override = CommonGrimArguments.PLATFORM_OVERRIDE.value();
if (override != null) return override;
if (ReflectionUtils.hasClass("io.papermc.paper.threadedregions.RegionizedServer")) return Platform.FOLIA;
if (ReflectionUtils.hasClass("org.bukkit.Bukkit")) return Platform.BUKKIT;
if (ReflectionUtils.hasClass("net.fabricmc.loader.api.FabricLoader")) return Platform.FABRIC;
throw new IllegalStateException("Unknown platform!");
final Map<String, Platform> platforms = Collections.unmodifiableMap(new HashMap<>() {{
put("io.papermc.paper.threadedregions.RegionizedServer", Platform.FOLIA);
put("org.bukkit.Bukkit", Platform.BUKKIT);
put("net.fabricmc.loader.api.FabricLoader", Platform.FABRIC);
}});
return platforms.entrySet().stream()
.filter(entry -> ReflectionUtils.hasClass(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Unknown platform!"));
}
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 +93,14 @@ public final class GrimAPI {
return loader.getPlatformPlayerFactory();
}
public ParserDescriptorFactory getParserDescriptors() {
return loader.getParserDescriptorFactory();
}
public InitManager getInitManager() {
return initManager;
}
public GrimPlugin getGrimPlugin() {
return loader.getPlugin();
}
@@ -122,10 +125,6 @@ public final class GrimAPI {
return loader.getMessagePlaceHolderManager();
}
public CommandService getCommandService() {
return loader.getCommandService();
}
private void checkInitialized() {
if (!initialized) {
throw new IllegalStateException("GrimAPI has not been initialized!");
@@ -136,7 +135,7 @@ public final class GrimAPI {
return loader.getPermissionManager();
}
public GrimExtensionManager getExtensionManager() {
return extensionManager;
public Platform getPlatform() {
return platform;
}
}
@@ -1,21 +1,21 @@
package ac.grim.grimac;
import ac.grim.grimac.api.GrimAbstractAPI;
import ac.grim.grimac.api.plugin.GrimPluginDescription;
import ac.grim.grimac.api.GrimUser;
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;
import ac.grim.grimac.utils.anticheat.LogUtil;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.chat.ChatUtil;
import ac.grim.grimac.utils.common.ConfigReloadObserver;
import com.github.retrooper.packetevents.netty.channel.ChannelHelper;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -43,13 +43,8 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
}
@Override
public @NotNull EventBus getEventBus() {
return api.getEventBus();
}
@Override
public @Nullable GrimUser getGrimUser(Player player) {
return getGrimUser(player.getUniqueId());
public @NonNull EventBus getEventBus() {
return api.getPluginManager().getEventBus();
}
@Override
@@ -57,6 +52,16 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
return api.getPlayerDataManager().getPlayer(uuid);
}
public String replaceVariables(GrimUser user, String content) {
for (Map.Entry<String, String> entry : staticReplacements.entrySet()) {
content = content.replace(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Function<GrimUser, String>> entry : variableReplacements.entrySet()) {
content = content.replace(entry.getKey(), entry.getValue().apply(user));
}
return content;
}
@Override
public void registerVariable(String string, Function<GrimUser, String> replacement) {
if (replacement == null) {
@@ -77,7 +82,8 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
@Override
public String getGrimVersion() {
return api.getGrimPlugin().getDescription().getVersion();
GrimPluginDescription description = GrimAPI.INSTANCE.getGrimPlugin().getDescription();
return description.getVersion();
}
@Override
@@ -114,11 +120,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);
@@ -132,7 +133,7 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
try {
GrimAPI.INSTANCE.getConfigManager().start();
} catch (Exception e) {
LogUtil.error("Failed to start config manager.", e);
e.printStackTrace();
}
}
@@ -165,14 +166,14 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
onReload(config);
if (started)
GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(),
() -> GrimAPI.INSTANCE.getEventBus().post(new GrimReloadEvent(true)));
() -> GrimAPI.INSTANCE.getPluginManager().callEvent(new GrimReloadEvent(true)));
return true;
} catch (Exception e) {
LogUtil.error("Failed to reload config", e);
e.printStackTrace();
}
if (started)
GrimAPI.INSTANCE.getScheduler().getAsyncScheduler().runNow(GrimAPI.INSTANCE.getGrimPlugin(),
() -> GrimAPI.INSTANCE.getEventBus().post(new GrimReloadEvent(false)));
() -> GrimAPI.INSTANCE.getPluginManager().callEvent(new GrimReloadEvent(false)));
return false;
}
@@ -187,15 +188,16 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
// Update variables
updateVariables();
// Restart
GrimAPI.INSTANCE.getAlertManager().reload(configManager);
GrimAPI.INSTANCE.getDiscordManager().reload();
GrimAPI.INSTANCE.getSpectateManager().reload();
GrimAPI.INSTANCE.getViolationDatabaseManager().reload();
// Don't reload players if the plugin hasn't started yet
if (!started) return;
// Reload checks for all players
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
player.runSafely(() -> player.reload(configManager));
for (GrimPlayer grimPlayer : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
ChannelHelper.runInEventLoop(grimPlayer.user.getChannel(), () -> {
grimPlayer.updatePermissions();
grimPlayer.reload(configManager);
});
}
}
@@ -210,7 +212,7 @@ public class GrimExternalAPI implements GrimAbstractAPI, ConfigReloadObserver, S
variableReplacements.putIfAbsent("%tps%", user -> String.format("%.2f", GrimAPI.INSTANCE.getPlatformServer().getTPS()));
variableReplacements.putIfAbsent("%version%", GrimUser::getVersionName);
// static variables
staticReplacements.put("%prefix%", MessageUtil.translateAlternateColorCodes('&', GrimAPI.INSTANCE.getConfigManager().getPrefix()));
staticReplacements.put("%prefix%", ChatUtil.translateAlternateColorCodes('&', GrimAPI.INSTANCE.getConfigManager().getPrefix()));
staticReplacements.putIfAbsent("%grim_version%", getGrimVersion());
}
}
@@ -8,19 +8,17 @@ 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 com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying;
import com.google.common.base.Preconditions;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import static com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying.isFlying;
// Class from https://github.com/Tecnio/AntiCheatBase/blob/master/src/main/java/me/tecnio/anticheat/check/Check.java
@Getter
public class Check extends GrimProcessor implements AbstractCheck {
protected @NotNull final GrimPlayer player;
protected @NonNull final GrimPlayer player;
public double violations;
private double decay;
@@ -41,7 +39,8 @@ public class Check extends GrimProcessor implements AbstractCheck {
private long lastViolationTime;
public Check(final @NotNull GrimPlayer player) {
this.player = Objects.requireNonNull(player);
Preconditions.checkArgument(player != null);
this.player = player;
final CheckData checkData = this.getClass().getAnnotation(CheckData.class);
if (checkData != null) {
@@ -61,19 +60,22 @@ 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() {
if (configName == null || player.platformPlayer == null) return;
final String id = configName.toLowerCase();
exemptPermission = player.platformPlayer.hasPermission("grim.exempt." + id);
noSetbackPermission = player.platformPlayer.hasPermission("grim.nosetback." + id);
noModifyPacketPermission = player.platformPlayer.hasPermission("grim.nomodifypacket." + id);
public void updatePermissions() {
if (player.platformPlayer == null || configName == null) return;
GrimAPI.INSTANCE.getScheduler().getEntityScheduler().run(
player.platformPlayer,
GrimAPI.INSTANCE.getGrimPlugin(),
() -> {
final String id = configName.toLowerCase();
exemptPermission = player.platformPlayer.hasPermission("grim.exempt." + id);
noSetbackPermission = player.platformPlayer.hasPermission("grim.nosetback." + id);
noModifyPacketPermission = player.platformPlayer.hasPermission("grim.nomodifypacket." + id);
},
() -> {}
);
}
public final boolean flagAndAlert(String verbose) {
@@ -97,7 +99,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
return false; // Avoid calling event if disabled
FlagEvent event = new FlagEvent(player, this, verbose);
GrimAPI.INSTANCE.getEventBus().post(event);
GrimAPI.INSTANCE.getPluginManager().callEvent(event);
if (event.isCancelled()) return false;
player.punishmentManager.handleViolation(this);
@@ -107,11 +109,7 @@ public class Check extends GrimProcessor implements AbstractCheck {
}
public final boolean flagWithSetback() {
return flagWithSetback("");
}
public final boolean flagWithSetback(String verbose) {
if (flag(verbose)) {
if (flag()) {
setbackIfAboveSetbackVL();
return true;
}
@@ -135,13 +133,14 @@ public class Check extends GrimProcessor implements AbstractCheck {
}
@Override
public final void reload(ConfigManager configuration) {
public void reload(ConfigManager configuration) {
decay = configuration.getDoubleElse(configName + ".decay", decay);
setbackVL = configuration.getDoubleElse(configName + ".setbackvl", setbackVL);
displayName = configuration.getStringElse(configName + ".displayname", checkName);
description = configuration.getStringElse(configName + ".description", description);
if (setbackVL == -1) setbackVL = Double.MAX_VALUE;
updatePermissions();
onReload(configuration);
}
@@ -169,15 +168,13 @@ public class Check extends GrimProcessor implements AbstractCheck {
return offset > 0.001 ? String.format("%.5f", offset) : String.format("%.2E", offset);
}
public static boolean isTransaction(PacketTypeCommon packetType) {
public boolean isTransaction(PacketTypeCommon packetType) {
return packetType == PacketType.Play.Client.PONG ||
packetType == PacketType.Play.Client.WINDOW_CONFIRMATION;
}
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;
public boolean isFlying(PacketTypeCommon packetType) {
return WrapperPlayClientPlayerFlying.isFlying(packetType);
}
public boolean isUpdate(PacketTypeCommon packetType) {
@@ -209,10 +206,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));
}
}
@@ -1,14 +0,0 @@
package ac.grim.grimac.checks.debug;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.player.GrimPlayer;
public abstract class AbstractDebugHandler extends Check {
public AbstractDebugHandler(GrimPlayer player) {
super(player);
}
public abstract boolean toggleListener(GrimPlayer player);
public abstract boolean toggleConsoleOutput();
}
@@ -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);
}
}
@@ -8,7 +8,7 @@ import ac.grim.grimac.utils.anticheat.update.RotationUpdate;
@CheckData(name = "AimDuplicateLook")
public class AimDuplicateLook extends Check implements RotationCheck {
private boolean exempt;
boolean exempt = false;
public AimDuplicateLook(GrimPlayer playerData) {
super(playerData);
@@ -28,7 +28,7 @@ public class AimModulo360 extends Check implements RotationCheck {
return;
}
if (player.yaw < 360 && player.yaw > -360 && Math.abs(rotationUpdate.getDeltaXRot()) > 320 && Math.abs(lastDeltaYaw) < 30) {
if (player.xRot < 360 && player.xRot > -360 && Math.abs(rotationUpdate.getDeltaXRot()) > 320 && Math.abs(lastDeltaYaw) < 30) {
flagAndAlert();
} else {
reward();
@@ -8,6 +8,7 @@ import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.lists.RunningMode;
import ac.grim.grimac.utils.math.GrimMath;
public class AimProcessor extends Check implements RotationCheck {
private static final int SIGNIFICANT_SAMPLES_THRESHOLD = 15;
@@ -18,10 +19,10 @@ public class AimProcessor extends Check implements RotationCheck {
public double divisorY;
public double modeX, modeY;
public double deltaDotsX, deltaDotsY;
private final RunningMode xRotMode = new RunningMode(TOTAL_SAMPLES_THRESHOLD);
private final RunningMode yRotMode = new RunningMode(TOTAL_SAMPLES_THRESHOLD);
private float lastXRot;
private float lastYRot;
RunningMode xRotMode = new RunningMode(TOTAL_SAMPLES_THRESHOLD);
RunningMode yRotMode = new RunningMode(TOTAL_SAMPLES_THRESHOLD);
float lastXRot;
float lastYRot;
public AimProcessor(GrimPlayer playerData) {
super(playerData);
@@ -37,6 +38,7 @@ public class AimProcessor extends Check implements RotationCheck {
public void process(final RotationUpdate rotationUpdate) {
rotationUpdate.setProcessor(this);
float deltaXRot = rotationUpdate.getDeltaXRotABS();
this.divisorX = GrimMath.gcd(deltaXRot, lastXRot);
@@ -45,6 +47,7 @@ public class AimProcessor extends Check implements RotationCheck {
this.lastXRot = deltaXRot;
}
float deltaYRot = rotationUpdate.getDeltaYRotABS();
this.divisorY = GrimMath.gcd(deltaYRot, lastYRot);
@@ -10,7 +10,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientHe
@CheckData(name = "BadPacketsA", description = "Sent duplicate slot id")
public class BadPacketsA extends Check implements PacketCheck {
private int lastSlot = -1;
int lastSlot = -1;
public BadPacketsA(final GrimPlayer player) {
super(player);
@@ -1,32 +0,0 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
@CheckData(name = "BadPacketsB", description = "Ignored set rotation packet")
public class BadPacketsB extends Check implements PacketCheck {
public BadPacketsB(final GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (isTransaction(event.getPacketType())) {
player.pendingRotations.removeIf(data -> {
if (player.getLastTransactionReceived() > data.getTransaction()) {
if (!data.isAccepted()) {
flagAndAlert();
}
return true;
}
return false;
});
}
}
}
@@ -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();
}
}
}
}
}
@@ -22,13 +22,15 @@ public class BadPacketsD extends Check implements PacketCheck {
final float pitch = new WrapperPlayClientPlayerFlying(event).getLocation().getPitch();
if (pitch > 90 || pitch < -90) {
// Ban.
if (flagAndAlert("pitch=" + pitch) && shouldModifyPackets()) {
// prevent other checks from using an invalid pitch
if (player.pitch > 90) player.pitch = 90;
if (player.pitch < -90) player.pitch = -90;
if (flagAndAlert("pitch=" + pitch)) {
if (shouldModifyPackets()) {
// prevent other checks from using an invalid pitch
if (player.yRot > 90) player.yRot = 90;
if (player.yRot < -90) player.yRot = -90;
event.setCancelled(true);
player.onPacketCancel();
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
@@ -14,8 +14,6 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPl
@CheckData(name = "BadPacketsE")
public class BadPacketsE extends Check implements PacketCheck {
private int noReminderTicks;
private final int maxNoReminderTicks = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) ? 20 : 19;
private final boolean isViaPleaseStopUsingProtocolHacksOnYourServer = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) || PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_2);
public BadPacketsE(GrimPlayer player) {
super(player);
@@ -23,17 +21,20 @@ public class BadPacketsE extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
final boolean isViaPleaseStopUsingProtocolHacksOnYourServer = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2) || PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21_2);
if (event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION ||
event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION) {
noReminderTicks = 0;
} else if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !player.packetStateData.lastPacketWasTeleport) {
if (++noReminderTicks > maxNoReminderTicks) {
flagAndAlert("ticks=" + noReminderTicks);
}
} else if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) {
noReminderTicks++;
} else if (event.getPacketType() == PacketType.Play.Client.STEER_VEHICLE
|| (isViaPleaseStopUsingProtocolHacksOnYourServer && player.inVehicle())) {
noReminderTicks = 0; // Exempt vehicles
}
if (noReminderTicks > 20) {
flagAndAlert("ticks=" + noReminderTicks); // ban? I don't know how this would false
}
}
public void handleRespawn() {
@@ -1,21 +1,23 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.BlockPlaceCheck;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.anticheat.update.BlockPlace;
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;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUseItem;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsH", description = "Sent unexpected sequence id", experimental = true)
public class BadPacketsH extends BlockPlaceCheck {
private int lastSequence;
private final boolean isSupportedVersion = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19);
@CheckData(name = "BadPacketsH", description = "Did not swing for attack")
public class BadPacketsH extends Check implements PacketCheck {
// 1.9 packet order: INTERACT -> ANIMATION
// 1.8 packet order: ANIMATION -> INTERACT
// I personally think 1.8 made much more sense. You swing and THEN you hit!
private boolean sentAnimation = player.getClientVersion().isNewerThan(ClientVersion.V_1_8);
public BadPacketsH(final GrimPlayer player) {
super(player);
@@ -23,45 +25,31 @@ public class BadPacketsH extends BlockPlaceCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM
&& shouldCancel(new WrapperPlayClientUseItem(event).getSequence())) {
event.setCancelled(true);
player.onPacketCancel();
}
}
if (event.getPacketType() == PacketType.Play.Client.ANIMATION) {
sentAnimation = true;
} else if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY) {
WrapperPlayClientInteractEntity packet = new WrapperPlayClientInteractEntity(event);
if (packet.getAction() != WrapperPlayClientInteractEntity.InteractAction.ATTACK) return;
@Override
public void onBlockPlace(BlockPlace place) {
if (shouldCancel(place.sequence) && shouldCancel()) {
place.resync();
}
}
// There is a "bug" in ViaRewind
// 1.8 packet order: ANIMATION -> INTERACT
// 1.9 packet order: INTERACT -> ANIMATION
// ViaRewind, on 1.9+ servers, delays a 1.8 client's ANIMATION to be after INTERACT (but before flying).
// Which means we see 1.9 packet order for 1.8 clients
// Due to ViaRewind also delaying the swings, we then see packet order above 20CPS like:
// INTERACT -> INTERACT -> ANIMATION -> ANIMATION
// I will simply disable this check for 1.8- clients on 1.9+ servers as I can't be bothered to find a way around this.
// Stop supporting such old clients on modern servers!
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9))
return;
@Override
public void onBlockBreak(BlockBreak blockBreak) {
switch (blockBreak.action) {
case START_DIGGING, FINISHED_DIGGING -> {
if (shouldCancel(blockBreak.sequence)) {
blockBreak.cancel();
}
}
case CANCELLED_DIGGING -> { // other actions will be checked by BadPacketsL
if (blockBreak.sequence != 0 && flagAndAlert("expected=0, id=" + blockBreak.sequence) && shouldModifyPackets()) {
blockBreak.cancel();
}
if (!sentAnimation && flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
sentAnimation = false;
}
}
public boolean shouldCancel(int sequence) {
int expected = lastSequence + 1;
lastSequence = sequence;
return isSupportedVersion && sequence != expected
&& flagAndAlert("expected=" + expected + ", id=" + sequence)
&& shouldModifyPackets();
}
public void onWorldChange() {
lastSequence = 0;
}
}
@@ -16,11 +16,13 @@ public class BadPacketsI extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.PLAYER_ABILITIES
&& new WrapperPlayClientPlayerAbilities(event).isFlying() && !player.canFly
&& flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
if (event.getPacketType() == PacketType.Play.Client.PLAYER_ABILITIES) {
if (new WrapperPlayClientPlayerAbilities(event).isFlying() && !player.canFly) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -1,58 +0,0 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.HeadRotation;
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;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientUseItem;
import java.util.ArrayList;
import java.util.List;
@CheckData(name = "BadPacketsJ", description = "Rotation in use item packet did not match tick rotation")
public class BadPacketsJ extends Check implements PacketCheck {
private final List<HeadRotation> rotations = new ArrayList<>();
public BadPacketsJ(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (!player.cameraEntity.isSelf()) {
rotations.clear();
return;
}
if (event.getPacketType() == PacketType.Play.Client.USE_ITEM && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21)
&& PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_21)) {
WrapperPlayClientUseItem packet = new WrapperPlayClientUseItem(event);
rotations.add(new HeadRotation(packet.getYaw(), packet.getPitch()));
}
if (isTickPacket(event.getPacketType())) {
// due to tick skipping, the rotations sent could be last tick's
boolean allowLast = player.canSkipTicks() && (event.getPacketType() == PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION || event.getPacketType() == PacketType.Play.Client.PLAYER_ROTATION);
for (HeadRotation rotation : rotations) {
if (rotation.yaw() == player.yaw && rotation.pitch() == player.pitch) {
allowLast = false;
continue;
}
if (rotation.yaw() == player.lastYaw && rotation.pitch() == player.lastPitch && allowLast) {
continue;
}
flagAndAlert();
}
rotations.clear();
}
}
}
@@ -16,11 +16,13 @@ public class BadPacketsK extends Check implements PacketCheck {
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.SPECTATE
&& player.gamemode != GameMode.SPECTATOR
&& flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
if (event.getPacketType() == PacketType.Play.Client.SPECTATE) {
if (player.gamemode != GameMode.SPECTATOR) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
}
@@ -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();
}
@@ -4,60 +4,57 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.Combat;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientClientStatus;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerChangeGameState;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerCombatEvent;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerDeathCombatEvent;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientInteractEntity;
@CheckData(name = "BadPacketsM", description = "Tried to respawn while alive", experimental = true)
@CheckData(name = "BadPacketsM")
public class BadPacketsM extends Check implements PacketCheck {
// 1.7 players do not send INTERACT_AT, so we cannot check them
private final boolean exempt = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10);
private boolean sentInteractAt = false;
public BadPacketsM(final GrimPlayer player) {
super(player);
}
// not a boolean because the server could send packets that cause
// the client to send a respawn packet before it receives the first
private int exempt;
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_STATUS && new WrapperPlayClientClientStatus(event).getAction() == WrapperPlayClientClientStatus.Action.PERFORM_RESPAWN) {
if (exempt > 0) {
exempt--;
return;
}
if (event.getPacketType() == PacketType.Play.Client.INTERACT_ENTITY && !exempt) {
if (!player.compensatedEntities.self.isDead) {
if (flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
}
final WrapperPlayClientInteractEntity wrapper = new WrapperPlayClientInteractEntity(event);
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.CHANGE_GAME_STATE && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)
&& new WrapperPlayServerChangeGameState(event).getReason() == WrapperPlayServerChangeGameState.Reason.WIN_GAME) {
player.addRealTimeTaskNow(() -> exempt++);
}
final PacketEntity entity = player.compensatedEntities.entityMap.get(wrapper.getEntityId());
if (event.getPacketType() == PacketType.Play.Server.DEATH_COMBAT_EVENT && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
if (new WrapperPlayServerDeathCombatEvent(event).getPlayerId() == player.entityID) {
player.addRealTimeTaskNow(() -> exempt++);
}
}
// For armor stands, vanilla clients send:
// - when renaming the armor stand or in spectator mode: INTERACT_AT + INTERACT
// - in all other cases: only INTERACT
// Just exempt armor stands to be safe
if (entity != null && entity.getType() == EntityTypes.ARMOR_STAND) return;
if (event.getPacketType() == PacketType.Play.Server.COMBAT_EVENT && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) {
WrapperPlayServerCombatEvent packet = new WrapperPlayServerCombatEvent(event);
if (packet.getCombat() == Combat.ENTITY_DEAD && packet.getPlayerId() == player.entityID) {
player.addRealTimeTaskNow(() -> exempt++);
switch (wrapper.getAction()) {
// INTERACT_AT then INTERACT
case INTERACT:
if (!sentInteractAt) {
if (flagAndAlert("Missed Interact-At") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentInteractAt = false;
break;
case INTERACT_AT:
if (sentInteractAt) {
if (flagAndAlert("Missed Interact") && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
sentInteractAt = true;
break;
}
}
}
@@ -2,10 +2,11 @@ package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "BadPacketsN", setback = 0)
public class BadPacketsN extends Check {
public class BadPacketsN extends Check implements PacketCheck {
public BadPacketsN(final GrimPlayer player) {
super(player);
}
@@ -4,6 +4,7 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.Pair;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@@ -11,10 +12,11 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientKe
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerKeepAlive;
import java.util.LinkedList;
import java.util.Queue;
@CheckData(name = "BadPacketsO")
public class BadPacketsO extends Check implements PacketCheck {
private final LinkedList<Long> keepalives = new LinkedList<>();
Queue<Pair<Long, Long>> keepaliveMap = new LinkedList<>();
public BadPacketsO(GrimPlayer player) {
super(player);
@@ -23,30 +25,37 @@ public class BadPacketsO extends Check implements PacketCheck {
@Override
public void onPacketSend(PacketSendEvent event) {
if (event.getPacketType() == PacketType.Play.Server.KEEP_ALIVE) {
keepalives.add(new WrapperPlayServerKeepAlive(event).getId());
WrapperPlayServerKeepAlive packet = new WrapperPlayServerKeepAlive(event);
keepaliveMap.add(new Pair<>(packet.getId(), System.nanoTime()));
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.KEEP_ALIVE) {
final long id = new WrapperPlayClientKeepAlive(event).getId();
WrapperPlayClientKeepAlive packet = new WrapperPlayClientKeepAlive(event);
for (long keepalive : keepalives) {
if (keepalive == id) {
// Found the ID, remove stuff until we get to it (to stop very slow memory leaks)
Long data;
do {
data = keepalives.poll();
} while (data != null && data != id);
long id = packet.getId();
boolean hasID = false;
return;
for (Pair<Long, Long> iterator : keepaliveMap) {
if (iterator.first() == id) {
hasID = true;
break;
}
}
if (flagAndAlert("id=" + id) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
if (!hasID) {
if (flagAndAlert("id=" + id) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
} else { // Found the ID, remove stuff until we get to it (to stop very slow memory leaks)
Pair<Long, Long> data;
do {
data = keepaliveMap.poll();
if (data == null) break;
} while (data.first() != id);
}
}
}
@@ -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", experimental = true)
public class BadPacketsP extends Check implements PacketCheck {
private int containerType = -1;
@@ -44,7 +44,7 @@ public class BadPacketsP extends Check implements PacketCheck {
case THROW -> button != 0 && button != 1;
case QUICK_CRAFT -> button == 3 || button == 7 || button > 10 || button < 0;
case PICKUP_ALL -> button != 0;
case UNKNOWN -> true;
case UNKNOWN -> false;
};
// Allowing this to false flag to debug and find issues faster
@@ -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 {
@@ -24,12 +25,11 @@ public class BadPacketsR extends Check implements PacketCheck {
long ms = (player.getPlayerClockAtLeast() - clock) / 1000000L;
long diff = (System.currentTimeMillis() - 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();
}
player.compensatedEntities.entitiesRemovedThisTick.clear();
player.compensatedWorld.removeInvalidPistonLikeStuff(oldTransId);
positions = 0;
clock = player.getPlayerClockAtLeast();
@@ -4,9 +4,6 @@ import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientWindowConfirmation;
@CheckData(name = "BadPacketsS")
public class BadPacketsS extends Check implements PacketCheck {
@@ -14,13 +11,4 @@ public class BadPacketsS extends Check implements PacketCheck {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.WINDOW_CONFIRMATION
&& !new WrapperPlayClientWindowConfirmation(event).isAccepted()
&& flagAndAlert() && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
}
}
@@ -10,25 +10,20 @@ 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")
public class BadPacketsT extends Check implements PacketCheck {
private final double maxHorizontalDisplacement;
private final double minVerticalDisplacement;
private final double maxVerticalDisplacement;
// 1.7 and 1.8 seem to have different hitbox "expansion" values than 1.9+
// https://github.com/GrimAnticheat/Grim/pull/1274#issuecomment-1872458702
// https://github.com/GrimAnticheat/Grim/pull/1274#issuecomment-1872533497
private final boolean hasLegacyExpansion = player.getClientVersion().isOlderThan(ClientVersion.V_1_9);
private final double maxHorizontalDisplacement = 0.3001 + (hasLegacyExpansion ? 0.1 : 0);
private final double minVerticalDisplacement = -0.0001 - (hasLegacyExpansion ? 0.1 : 0);
private final double maxVerticalDisplacement = 1.8001 + (hasLegacyExpansion ? 0.1 : 0);
public BadPacketsT(final GrimPlayer player) {
super(player);
// 1.7 and 1.8 seem to have different hitbox "expansion" values than 1.9+
// https://github.com/GrimAnticheat/Grim/pull/1274#issuecomment-1872458702
// https://github.com/GrimAnticheat/Grim/pull/1274#issuecomment-1872533497
double expansion = player.getClientVersion().isOlderThan(ClientVersion.V_1_9) ? 0.1 : 0;
maxHorizontalDisplacement = 0.3001 + expansion;
minVerticalDisplacement = -0.0001 - expansion;
maxVerticalDisplacement = 1.8001 + expansion;
}
@Override
@@ -36,39 +31,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.getType())) {
// 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);
});
}
}
}
@@ -2,10 +2,11 @@ package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
@CheckData(name = "BadPacketsW", description = "Interacted with non-existent entity", experimental = true)
public class BadPacketsW extends Check {
public class BadPacketsW extends Check implements PacketCheck {
public BadPacketsW(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.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;
}
@@ -1,32 +0,0 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "BadPacketsZ", experimental = true)
public class BadPacketsZ extends Check implements PacketCheck {
private boolean sent;
public BadPacketsZ(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
sent = false;
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_INPUT) {
if (sent) {
flagAndAlert();
}
sent = true;
}
}
}
@@ -9,7 +9,7 @@ import ac.grim.grimac.utils.anticheat.update.RotationUpdate;
import ac.grim.grimac.utils.data.HeadRotation;
import ac.grim.grimac.utils.math.GrimMath;
// This check has been patched by Baritone for a long time, and it also seems to false with cinematic camera now, so it is disabled.
// This check has been patched by Baritone for a long time and it also seems to false with cinematic camera now, so it is disabled.
@CheckData(name = "Baritone")
public class Baritone extends Check implements RotationCheck {
private int verbose;
@@ -23,10 +23,10 @@ public class Baritone extends Check implements RotationCheck {
final HeadRotation from = rotationUpdate.getFrom();
final HeadRotation to = rotationUpdate.getTo();
final float deltaPitch = Math.abs(to.pitch() - from.pitch());
final float deltaPitch = Math.abs(to.getPitch() - from.getPitch());
// Baritone works with small degrees, limit to 1 degree to pick up on baritone slightly moving aim to bypass anticheats
if (rotationUpdate.getDeltaXRot() == 0 && deltaPitch > 0 && deltaPitch < 1 && Math.abs(to.pitch()) != 90.0f) {
// Baritone works with small degrees, limit to 1 degrees to pick up on baritone slightly moving aim to bypass anticheats
if (rotationUpdate.getDeltaXRot() == 0 && deltaPitch > 0 && deltaPitch < 1 && Math.abs(to.getPitch()) != 90.0f) {
if (rotationUpdate.getProcessor().divisorY < GrimMath.MINIMUM_DIVISOR) {
verbose++;
if (verbose > 8) {
@@ -12,7 +12,7 @@ import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
import com.github.retrooper.packetevents.util.Vector3i;
import org.jetbrains.annotations.NotNull;
import org.checkerframework.checker.nullness.qual.NonNull;
@CheckData(name = "AirLiquidBreak", description = "Breaking a block that cannot be broken")
public class AirLiquidBreak extends Check implements BlockBreakCheck {
@@ -20,8 +20,8 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
private int lastTick;
private boolean didLastFlag;
// Initialize to non-null values to prevent NPE when checking for blockType properties and if position equals old position
private @NotNull Vector3i lastBreakLoc = new Vector3i();
private @NotNull StateType lastBlockType = StateTypes.AIR;
private @NonNull Vector3i lastBreakLoc = new Vector3i();
private @NonNull StateType lastBlockType = StateTypes.AIR;
public AirLiquidBreak(GrimPlayer player) {
super(player);
@@ -51,7 +51,7 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
lastBlockType = block;
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.inventory.getHeldItem().is(ItemTypes.LIGHT) || player.inventory.getOffHand().is(ItemTypes.LIGHT)))
boolean invalid = (block == StateTypes.LIGHT && !(player.getInventory().getHeldItem().is(ItemTypes.LIGHT) || player.getInventory().getOffHand().is(ItemTypes.LIGHT)))
|| block.isAir()
|| block == StateTypes.WATER
|| block == StateTypes.LAVA
@@ -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,14 +20,15 @@ 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;
for (double d : player.getPossibleEyeHeights()) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
}
// getPickRange() determines this?
@@ -7,8 +7,6 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed;
import ac.grim.grimac.utils.viaversion.ViaVersionUtil;
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.protocol.player.ClientVersion;
@@ -30,14 +28,13 @@ public class FastBreak extends Check implements BlockBreakCheck {
// For some reason these states flag and I don't know why.
// Better to just exempt to not annoy legit players.
private static final Set<StateType> EXEMPT_STATES = Set.of();
private final boolean clientOlderThanServer = PacketEvents.getAPI().getServerManager().getVersion().getProtocolVersion() > player.getClientVersion().getProtocolVersion();
public FastBreak(GrimPlayer playerData) {
super(playerData);
}
// The block the player is currently breaking
Vector3i targetBlockPosition = null;
Vector3i targetBlock = null;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
@@ -53,24 +50,18 @@ public class FastBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
if (!ViaVersionUtil.isAvailable) {
// Exempt all blocks that do not exist in the player version
final WrappedBlockState defaultState = WrappedBlockState.getDefaultState(player.getClientVersion(), blockBreak.block.getType());
if (defaultState.getType() == StateTypes.AIR || EXEMPT_STATES.contains(defaultState.getType())) {
return;
}
WrappedBlockState block = blockBreak.block;
// Exempt all blocks that do not exist in the player version
final WrappedBlockState defaultState = WrappedBlockState.getDefaultState(player.getClientVersion(), block.getType());
if (defaultState.getType() == StateTypes.AIR || EXEMPT_STATES.contains(defaultState.getType())) {
return;
}
// If client is older than the server, fetch block client actually sees from via
// otherwise just return the server-side block (since if client is >= server version the block is guaranteed to exist in client version)
// TODO this lazy loads PacketEvents mappings for older versions for clients on versions older than the servers, increasing memory usage
// * its the only thing we use non-native mappings for behind ViaVersion
// * 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); // ???
targetBlockPosition = blockBreak.position;
startBreak = System.currentTimeMillis() - (targetBlock == null ? 50 : 0); // ???
targetBlock = blockBreak.position;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, block);
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, targetBlock);
double breakDelay = System.currentTimeMillis() - lastFinishBreak;
@@ -80,8 +71,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();
}
@@ -90,7 +80,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
clampBalance();
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockPosition != null) {
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlock != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;
@@ -103,8 +93,7 @@ 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();
}
@@ -119,8 +108,8 @@ public class FastBreak extends Check implements BlockBreakCheck {
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockPosition != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockPosition)));
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlock != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, targetBlock));
}
}
@@ -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;
}
}
Loaded 100 of 627 files, more files were not shown because too many files have changed in this diff. Show more