Compare commits

...
Author SHA1 Message Date
NotAlexNoyle b195cb658f Exclude CODEOWNERS from language statistics 2026-10-29 16:18:57 -04:00
NotAlexNoyle 8437088abf Add CODEOWNERS (one staff approval) 2026-10-29 16:18:57 -04:00
NotAlexNoyle 6a72263909 Update lang 2026-09-14 00:54:39 -04:00
NotAlexNoyle ff04996ca9 Tune fastbreak for 1.21.11 ViaVersion clients 2026-08-17 10:43:04 -04:00
NotAlexNoyle 46358b3079 Normalize adventure version with server 2026-08-10 07:49:27 -04:00
NotAlexNoyle d06bb80891 Fix postgres, SLF4J 2026-06-17 13:23:42 -04:00
NotAlexNoyle e29e8b6e9f Fix postgres, slf4j providers 2026-06-16 23:10:23 -04:00
NotAlexNoyle 9d1d5a4295 Harmonize config with TrueOG NCP 2026-06-16 11:21:44 -04:00
NotAlexNoyle 9ed8ae9dba New PacketEvents repo 2026-06-03 18:19:47 -04:00
NotAlexNoyle bebcab70b0 Standardize PacketEvents 2026-06-03 17:58:54 -04:00
NotAlexNoyle db66c035c2 Backport to PacketEvents 2.11.2 API 2026-06-01 19:50:43 -04:00
NotAlexNoyle 67da895f25 Merge PR #3 upstream updates while preserving legacy item bobbing fix 2026-06-01 14:57:29 -04:00
NotAlexNoyle 8909e710ae Fix bobbing on 1.8-1.15.2 clients 2026-06-01 14:30:17 -04:00
Axionize 65815fc73b Merge remote-tracking branch 'origin/2.0' into lightning
# Conflicts:
#	common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java
2026-04-15 10:17:13 -04:00
Axionize c169d9c682 Fix incorrect entity metadata indexes 2026-04-14 04:24:31 -04:00
ManInMyVan 99dce8c28d don't check the same value twice 2026-04-13 19:51:59 -06:00
Bram 1b60cb82c5 Fixed: MultiActionsD not checking if the inventory got opened this tick (#2596)
Fixed: MultiActionsD cancelling the close window packet causing desync / compatibility issues
Fixed: CheckManagerListener not using the open horse window to set serverOpenedInventoryThisTick
2026-04-13 16:33:41 -06:00
NotAlexNoyle 11facb64c0 Disable update checking 2026-04-13 07:29:50 -04:00
Milan Ensink 35fe1f0768 Fix MYSQL 8 support (#2534)
* Fix MYSQL 8 support

* Prevent double index error from sending everytime.
2026-04-13 04:51:40 -04:00
ManInMyVan fc75e51a09 fix PacketOrderC viabackwards false 2026-04-12 18:21:25 -06:00
ManInMyVan 13ad8bd823 fix #2579 2026-04-12 18:18:54 -06:00
Tyler 890799ddf9 Bumped Gradle Wrapper to fix build (#2591) 2026-04-12 09:01:19 -04:00
Axionize 3e07a0ee2d Downgrade PE to fix promise/future execution breaking in PE's netty listeners 2026-04-12 08:22:07 -04:00
ManInMyVan e8dc9ae997 RESOURCE_PACK_STATUS is async 2026-04-10 14:50:24 -06:00
ManInMyVan b55a12c721 minor changes to PacketHidePlayerInfo 2026-04-10 13:12:05 -06:00
Axionize ece9f3cae4 Bump PE to support Bukkit 26.1 servers 2026-04-09 11:12:12 -04:00
Fussel efd796cdd6 Fix PostgreSQL UUID retrieval in history command (#2575)
* Fix PostgreSQL UUID retrieval in history command

* Improve error handling for UUID retrieval in DatabaseUtils

* Use if-else if in DatabaseUtils.getUuid
2026-04-08 20:36:45 -04:00
ManInMyVan b9da63c8e2 fix #1990 2026-04-06 17:37:54 -06:00
SamB440 7fd7bc1d7b Fix soul torch collision data 2026-04-06 17:50:14 +01:00
ManInMyVan d1b2135191 add BadPacketsC to check if the LEAVE_BED packet is valid 2026-04-04 19:57:24 -06:00
SamB440 10cb176992 Fix glow squid bounding box 2026-04-04 17:55:26 +01:00
ManInMyVan 77c48f4942 quilt is a normal client 2026-04-03 14:12:54 -06:00
ManInMyVan cd3576b196 fix #2563 2026-04-02 20:54:03 -06:00
ManInMyVan 89eedbeb85 actually check sneaking in MultiInteractA 2026-04-02 20:36:39 -06:00
ManInMyVan 7d9dc86ec7 account for INTERACT_ENTITY being split in 26.1 2026-04-02 20:32:24 -06:00
ManInMyVan 11d572f8fd move ElytraA onStartGliding() to before the client version check in PacketEntityAction 2026-04-01 19:06:19 -06:00
SamB440 6a46c359c4 Check if isPacketInventoryActive was false before allowing revert 2026-03-31 16:32:12 +01:00
Axionize 9b06c8670e Fix PE 26.1 update (deferred fabric folder) 2026-03-29 11:40:53 -04:00
Axionize f380fcd9af Bump PE for initial 26.1 support 2026-03-29 10:39:26 -04:00
AoElite f0e15b0521 add separate logic for publishing release builds in workflow 2026-03-27 05:33:49 -04:00
ManInMyVan bf10e23e8e spear stabs are attacks, handle them as such 2026-03-26 20:56:41 -06:00
NotAlexNoyle 598a44ed56 Fix PacketEvents API 2.11.2 on 1.19.4 for 1.8-1.21.11 clients 2026-03-25 07:55:01 -04:00
NotAlexNoyle 8897fc87e6 Use PacketEvents API 2.11.2 2026-03-25 07:45:13 -04:00
NotAlexNoyle 252deb7fd7 Merge latest version from upstream 2026-03-25 05:45:28 -04:00
Axionize 5efefe59ec Fix compilation issues, eliminate allocations and fix distanceSquared() > 36 instead of 6 2026-03-25 03:59:12 -04:00
Axionize 162a8d1b23 Merge branch '2.0' into lightning
# Conflicts:
#	common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java
#	common/src/main/java/ac/grim/grimac/manager/CheckManager.java
#	common/src/main/java/ac/grim/grimac/player/GrimPlayer.java
2026-03-25 03:36:40 -04:00
Axionize 740f7f4aa7 Make DefaultResyncHandler a record 2026-03-25 03:28:58 -04:00
Axionize 5f9b9ddb65 Bump Kotlin Plugin Serialization 2.3.0 2026-03-25 03:28:32 -04:00
ManInMyVan e40ec015b9 footer text does not get parsed as markdown 2026-03-21 15:56:50 -06:00
ManInMyVan e954124802 check for keepsprint on 1.9 servers (#2070)
* check for keepsprint on 1.9 servers

* update held item in AttackCooldownHandler every time the slot is changed

* fix #2389

* change AttackCooldownHandler package
2026-03-21 15:46:58 -06:00
NotAlexNoyle 4a374d84be Merge pull request #1 from true-og/codex/rename-plugin-to-lightninggrim
Rename remaining branding to LightningGrim
2026-03-21 13:51:15 -04:00
ManInMyVan 598aae0ec8 require hand to be MAIN_HAND in PacketOrderB 2026-03-20 17:47:11 -06:00
ManInMyVan 48115eb7aa fix noslow with invalid drop item packets 2026-03-19 21:16:43 -06:00
ManInMyVan 68f34f2a31 make TransactionOrder verbose consistent with other checks 2026-03-18 20:26:23 -06:00
ManInMyVan 3055cd10de set CheckManager#packetEntityReplication on init 2026-03-18 20:20:54 -06:00
ManInMyVan 177c00355c rename BadPacketsC to SelfInteract 2026-03-18 20:15:14 -06:00
NotAlexNoyle f6df1b768d Rename remaining plugin branding to LightningGrim 2026-03-18 14:29:45 -04:00
ManInMyVan 539e822615 slightly change BadPacketsP description 2026-03-15 19:25:20 -06:00
ManInMyVan 29c0757bbb exempt spectators in 1.21.11+ from PacketOrderB (fixes #2493) 2026-03-15 18:38:18 -06:00
RareMen 13fb273501 Fix ghost block mitigation on floating block placement (#2530) 2026-03-12 23:58:37 -06:00
SamB440 574946391c Reset food level on respawn (#2528) 2026-03-11 17:25:47 +00:00
Axionize 0a1ce1d88b Fix encoding of Polymer blocks breaking when resyncing with world, optimize allocations Fabric PlatformChunk.getBlockId(x,y,z) 2026-03-10 20:42:26 -04:00
Cyramek 7e8b6b620e add support for viaversion attack range component for 1.21.11+ clients on legacy servers (#2474)
add support for https://github.com/ViaVersion/ViaVersion/pull/4733
2026-03-09 21:56:12 +01:00
cyramek 546abf07fb bump version to 2.3.74 2026-03-09 20:17:54 +01:00
Cyramek 8a0d1e274a account for 1.21.2+ clients ignoring teleport packets while in vehicle (#2470)
fix: https://github.com/GrimAnticheat/Grim/issues/2463
2026-03-09 19:53:29 +01:00
Cyramek 302bed1bc1 fix many issues with collisions while riding nautilus/happyghast (#2460)
thanks LaserSlime and Axionize for help!
2026-03-09 19:53:15 +01:00
Axionize 879d566c05 Add support disable-webhook-validation boolean in discord.yml for custom domains with Discord webhooks
Closes https://github.com/GrimAnticheat/Grim/issues/2520
2026-03-09 04:48:25 -04:00
Axionize d4611f1b31 discord.yml translation fixes 2026-03-09 04:37:22 -04:00
Axionize 53cf3e8e99 Fix compile issues from package references not being refactored in Discord refactor 2026-03-09 04:05:17 -04:00
Axionize c356f5fa4d Fix Discord Webhook PAPI Placeholders (#2518)
* Fix support for Discord markdown parsing of several edge cases

* Fix PAPI name mangling

* Cleanup Implementation of new Discord Webhook handling

* Document exploit choice to not use volatile when technically correct to

* Address all code review concerns

* Cleanup unused code

* Refactor packages for discord related data types
2026-03-09 03:58:17 -04:00
ManInMyVan d273229487 remove unused imports in Reach.java 2026-03-08 15:34:03 -06:00
ManInMyVan c7983b7e37 Revert "load after ZNPCsPlus"
This reverts commit 1d0cbb7078.
2026-03-08 15:33:53 -06:00
dxw1dandDawixTV 272a8892db Add Polish configuration file for GrimAC (#2501)
Co-authored-by: DawixTV <92529087+DawixTV@users.noreply.github.com>
2026-03-04 14:41:23 -05:00
Axionize 439477eff1 Merge PR #2267 - Improved Chinese Translations 2026-03-04 14:35:43 -05:00
Axionize 1f55742d76 Clean up typos and translator notes 2026-03-04 14:31:52 -05:00
Axionize b281a2d158 add support for attribute swapping to and from items with attack range components 2026-03-03 16:24:35 -05:00
Axionize 7d487a4279 history rewrite marker 2026-03-02 15:25:25 -05:00
Axionize f0fbc9db8c Fix getting hitbox for unstable scaffolding 2026-03-02 14:09:50 -05:00
Axionize 0a6ac7a47e Merge branch '2.0' into lightning
# Conflicts:
#	.github/workflows/build-and-publish.yml
#	common/src/main/java/ac/grim/grimac/checks/impl/combat/Reach.java
#	common/src/main/java/ac/grim/grimac/predictionengine/PointThreeEstimator.java
2026-02-26 03:02:12 -05:00
Axionize 4ca65b1670 Update Grim API to 1.2.4.0 2026-02-23 02:23:51 -05:00
AoElite 7be8f51267 bump grim-api version 2026-02-22 17:34:04 -05:00
ManInMyVan 1d0cbb7078 load after ZNPCsPlus 2026-02-22 10:24:30 -07:00
MatrixU5er b65b7f0f58 Improve comments in Chinese configuration file (#2490)
Updated comments in zh.yml for clarity and translation.
2026-02-15 13:34:45 +00:00
ManInMyVan 327b947c08 Add /grim testwebhook 2026-02-13 15:37:14 -07:00
CubicakeandManInMyVan 2b621483e7 Add method exposing int block type from CompensatedWorld (#2464)
* Implement feature

* Update common/src/main/java/ac/grim/grimac/utils/latency/CompensatedWorld.java

Co-authored-by: ManInMyVan <113963219+ManInMyVan@users.noreply.github.com>

* Add @Override annotation

---------

Co-authored-by: ManInMyVan <113963219+ManInMyVan@users.noreply.github.com>
2026-02-01 08:29:08 -05:00
runningbird2 f118b5c781 fix: honor per-check nomodifypacket (#2477) 2026-01-30 08:35:33 -07:00
ManInMyVan f95d877025 fix elytra packet abuse on 1.8 servers 2026-01-27 17:29:13 -07:00
Axionize 6c49a01853 Bump PE: Fixes Packet Ordering and mapping issues
- Previa packet mapping failing due to incorrect upstream call to getPacketVersion() instead of getClientVersion(), affects previa on all versions
- Fix Krypton + Via + Grim compat form listener relocation
2026-01-24 00:15:40 -05:00
SamB440 80afaae74f Evoker fangs are unhittable 2026-01-20 19:47:01 +00:00
Axionize 35174fdca9 Refactor Resolver Lifecycle 2026-01-18 16:56:10 -05:00
Axionize 88cfffc5d6 Rename Fabric1211PlatformServer -> Fabric12111PlatformServer 2026-01-18 16:56:10 -05:00
SamB440 cd86c14709 Don't let player reuse setback velocity (fixes #2445) (#2450) 2026-01-17 10:20:13 +00:00
cyramek b7a719dc2e revert this for now, until we find a better way to fix it 2026-01-15 15:31:52 +01:00
Ayou c04335773b Fix fast break false when switching tools via shulker box (#2454) 2026-01-14 13:33:34 +00:00
Axionize 9d1682d397 Rename Fabric mc1211 -> mc12111 module 2026-01-14 00:23:21 -05:00
Axionize fefe0d150f Fix early init of CommandManager on Bukkit causing startup issues 2026-01-13 16:44:45 -05:00
Axionize a54329ad45 Bump PE 2026-01-13 14:18:45 -05:00
Axionize 36590605b1 Update supported fabric versions 2026-01-13 14:17:55 -05:00
Axionize 075cec8c12 Fabric 1.21.11 support for fabric-permissions-api-v0 not being included 2026-01-13 02:34:42 -05:00
Axionize b9fa89ccb9 Refactor Fabric to make permissions API and cloud optional dependencies 2026-01-13 01:49:52 -05:00
Axionize 08770f0c6b Command API fallback handling 2026-01-12 21:42:01 -05:00
Axionize 50a25d9921 Bump API to no longer shade Player shim 2026-01-12 20:02:48 -05:00
Axionize 21f69886b8 Bump PE 2026-01-10 16:30:34 -05:00
Axionize 8001f6cb1d Fix support for messing legacy getGrimUser(Player player) method 2026-01-07 21:49:44 -05:00
Axionize c5855fd58d Unify all APIs 2026-01-07 21:17:45 -05:00
CyramekandAxionize bf8586393a 1.21.11 support (#2420)
* fix horse modern/legacy jumping mechanics, camel dash cooldown tracking (#2199)

* implement UseEffects component

fixes: https://github.com/GrimAnticheat/Grim/issues/2396

* initial support for hitbox margin

* compensableDashableEntities -> dashableEntities

* implement floating in water

* implement nautilus movement

todo: fix dashing, fix step

* fix 0.03 fluid pushing

this code was dropped in:
https://github.com/GrimAnticheat/Grim/commit/1a6a8c57583ef12556241383829eb5f7dcc3595f

* fix nautilus dashing

* improve precision while dashing

* actually fix dashing

I'm really dumb ngl, vanilla packet sequence:
1. rot packet with PLAYER look angle
2. vehicle move packet with ENTITY look angle

vehicle move packet overrides player's yaw and pitch from first rot packet

* implement all prediction engines for nautilus

I will refactor this later

* support new gliding behavior

* refactor nautilus prediction engines

* implement parched boundingbox

* add support for fast lava gameplay attribute in fluid pushing

* camels have variants now

* WIP tick-based world borders

* fix off ground camel dashing

thanks thisisatest8406

* add onGround tracking for entities

* revert tracking entity onGround

I decided that I don't care enough, camels are bugged as hell, everything desync

* remove unneeded diff

* fix pending jumps and camel off-ground dashing

* remove outdated comment regarding nautilus jumping logic

* Add support for max_reach AttackRange component

---------

Co-authored-by: Axionize <154778082+Axionize@users.noreply.github.com>
2026-01-07 11:25:27 -05:00
Axionize 95e2774f59 Add Back Support for Legacy API (#2409)
* Add back support for the legacy API temporarily

* Bump API to release version
2026-01-06 18:04:54 -05:00
Axionize 5c17fba090 Merge remote-tracking branch 'origin/2.0' into lightning 2026-01-06 15:12:16 -05:00
Axionize 02f6a3b51b Faster temporary fix for Folia breaking getTPS() 2026-01-06 15:03:45 -05:00
Axionize dbd75108c8 Temporary fix for Folia breaking getTPS() 2026-01-06 15:00:23 -05:00
Axionize 9cb45476ea Fix rot only accounting for reach/hitbox in 1.15 - 1.20.1 and 1.21.5 - 1.21.8 once and for all 2026-01-06 14:35:42 -05:00
Axionize be35b9b481 Update .gitignore 2026-01-05 15:10:21 -05:00
Axionize ca64aabaa2 Fix merge errors in hitbox patch 2026-01-05 15:08:41 -05:00
Axionize 801658600c Add forgotten util class file for hitbox fix 2026-01-05 15:06:23 -05:00
Axionize a2074d0fe4 Fix Coral hitboxes 2026-01-05 14:50:41 -05:00
Axionize 14573dace1 Fix Mushroom and Fungus Hitboxes 2026-01-04 21:00:20 -05:00
Axionize 639490a825 Attempt to fix disconnect.timeout cascade issues 2026-01-04 14:49:44 -05:00
Axionize 07d201004e improve FabricatedPlace patch 2026-01-04 14:49:44 -05:00
ManInMyVan 6186abc7e7 make CrashC verbose more consistent with other checks 2026-01-03 19:01:25 -07:00
cyramek 29d28c64fc refactor BlockEffectsResolver to accept movement list 2026-01-03 22:19:41 +01:00
Axionize 98fd065ddb Patch FabricatedPlace falses 2026-01-01 12:59:53 -05:00
Axionize d0b30ded6c Only update profiles if PLAYER_INFO_UPDATE contains ADD_PLAYER action 2026-01-01 10:56:32 -05:00
Axionize b79d5fdaaf Fix Sculk Vein replacement Phase/Simulation falses & Optimize canBeReplaced logic
- Added replacement logic for SCULK_VEIN (mirrors GLOW_LICHEN).
- Refactored sequential 'if' statements into an 'else-if' chain to improve branch prediction and allow early exits.
- Reordered block checks based on frequency (Slabs, Snow, and Vines are now checked first).
- Merged duplicate logic for multiface blocks.
2026-01-01 08:01:05 -05:00
Axionize 7c68947731 Add option to disable resyncing without API with a NoOp resync handler 2026-01-01 07:14:46 -05:00
Axionize 80f90e658f resync on netty thread instead of trying to send packets in region thread 2025-12-31 20:02:55 -05:00
Axionize 101473b913 Prevent torn reads and race condition on respawn on Fabric from reconstruction of ServerPlayerEntity 2025-12-31 16:22:35 -05:00
SamB440 42b6630c55 Use config history.database.port for database connection port. Fixes #2415 2025-12-31 20:51:54 +00:00
Axionize daa3bfe186 Merge branch '2.0' into lightning 2025-12-31 09:10:14 -05:00
Axionize 4292e93327 Merge branch 'fix/reach-infinite-expanding-hitbox' into 2.0 2025-12-31 09:03:06 -05:00
Axionize 60b70d1a11 Fix reach regression in not flagging clients from versions 1.15 to 1.20.1 due to rot only regression 2025-12-31 09:00:26 -05:00
Axionize ce0dad56ad Add PlatformPlayerFactory#getOfflinePlayers() 2025-12-30 07:38:44 -05:00
Axionize 90193b47d6 Stash work 2025-12-30 06:25:24 -05:00
cyramek 9e745fdba5 fix NPE due to incorrect initialization order 2025-12-29 19:49:20 +01:00
ManInMyVan d374c2bc08 support CAMERA packet (#2303) 2025-12-29 11:13:28 -07:00
ManInMyVan 2d8def6eec clean up OptifineFastMath.java a bit 2025-12-27 16:56:34 -07:00
Axionize 83794571d5 Merge remote-tracking branch 'origin/2.0' into lightning
# Conflicts:
#	.github/workflows/build-and-publish.yml
2025-12-24 02:22:13 -05:00
Rothes 8f0036e4e9 Cache map values for performance (#2407) 2025-12-24 02:14:44 -05:00
Axionize b6f9f86d11 chore: Bump PE 2025-12-21 06:24:39 -05:00
Axionize 55da3842f5 chore: Increase default building memory allocation 2025-12-20 00:24:11 -05:00
Axionize 955b725884 chore: Bump shadow 2025-12-20 00:20:14 -05:00
Axionize 96267128fc Fix: build ci changes 2025-12-20 00:16:07 -05:00
Axionize 92d06e4222 Bump PE 2025-12-20 00:02:35 -05:00
uRyanxD 5f02ccc4fe Fix NPE when Spigot late-bind option is enabled & improve injection logic
This PR fixes a NullPointerException that occurred when the Spigot "late-bind" option was enabled on servers prior to 1.14.4.

Changes:
- Changed injection priority: The plugin now attempts to register the Paper TickEndEvent first. It only falls back to NMS reflection if the Paper API is unavailable.
- Added a null check for the server connection instance to prevent the NPE during reflection injection.
- Added specific error logging: If injection fails on legacy servers (< 1.14.4), the plugin now reflectively checks `spigot.yml` settings to explicitly warn the user if `late-bind` is the cause.
- Refactored reflection logic to use `ReflectionUtils` for better safety and code cleanliness.
2025-12-19 23:54:00 -05:00
Axionize f772b745cc ci: make runner configurable to work around Tenki issues (#2398)
Tenki's self-hosted runners started failing on Dec 19, 2025 with
"Invalid URL" errors during artifact upload. This adds a configurable
runner input to all workflows, allowing easy switching between
GitHub-hosted and Tenki runners.

- Add `runner` input to reusable build workflow (default: ubuntu-latest)
- Expose runner option in gradle-publish.yml for manual runs  
- Explicitly set runner in release.yml
- Bump upload-artifact v4 → v6
2025-12-19 23:22:02 -05:00
Axionize 9bd12cc7a2 Bump github actions-upload-artifact versions 2025-12-19 18:08:30 -05:00
iLemon 32f607b597 Fix gliders not working in offhand slot (#2397) 2025-12-19 03:28:29 -05:00
Axionize c2afc62241 fix: bump gradle wrapper to fix build 2025-12-17 05:49:51 -05:00
Axionize 97b875860f build: add initial support for 1.21.11 and update dependencies
- Update GitHub workflow game-versions to >=1.21.11
- Update Cloud Command Framework (Paper: beta.14, Fabric: beta.15)
- Update Fabric Permissions API to 0.6.1
- Update Fabric Loom to 1.14.6
- Update Shadow Gradle plugin to 9.0.0 (stable)
2025-12-17 01:37:50 -05:00
Axionize c019566255 fix: Bubble Column, Azalea, Bed, and Ceiling Wall Sign hit/collision boxes 2025-12-17 00:42:32 -05:00
Axionize 12f6af3715 Bump PE 2025-12-17 00:42:32 -05:00
Axionize 965008f7e1 build: Refactor repo logic to support mavenLocalOverride
- Implements a `RepositoryHandler.exclusive` extension in buildSrc to handle repository priority.
- Fixes an issue where strict `exclusiveContent` blocks prevented `mavenLocal()` from being checked even when `-PmavenLocalOverride=true` was active.
- Ensures efficient dependency resolution: checks Local (if enabled) -> Remote -> Stop, preventing leaked HTTP requests.
- Reduces boilerplate code across common, bukkit, and fabric build configurations.
- Adds overload support for existing repositories (e.g., mavenCentral) within exclusive blocks.
2025-12-17 00:42:32 -05:00
Axionize 4a104e287c fix: exclude Azalea from Sapling hitbox definitions
Azaleas were incorrectly treated as saplings giving them an incorrect hitbox. This removes them from the sapling list in HitboxData so they fall back to the correct default collision box.
2025-12-17 00:42:32 -05:00
AoElite 0153ce3a4d Revert "fix incorrect EquipmentSlot mappings in isGlider logic"
This reverts commit d96d683b14.
2025-12-14 05:01:46 -05:00
AoElite d96d683b14 fix incorrect EquipmentSlot mappings in isGlider logic 2025-12-14 04:56:42 -05:00
AoElite acf273fd1f adjust isGlider logic to account for items that can't be depleted 2025-12-14 04:49:16 -05:00
AoElite 679e869a00 adjust canGlide logic to try to improve compatibility 2025-12-14 04:37:35 -05:00
Axionize 324469a78a Merge remote-tracking branch 'origin/2.0' into lightning 2025-12-11 14:32:13 -05:00
AoElite a1b4893d88 fix null pointer when checking if bread was a setback 2025-12-07 22:12:38 -05:00
NotAlexNoyle e4c4642984 Fix compatibility with TrueOG Bootstrap 2025-12-07 21:26:44 -05:00
SamB440 4d28f85665 More setback changes 2025-12-06 13:24:27 +00:00
AoElite 0d22e34c5a refactor BlockBreakSpeed class for improved maintainability 2025-12-05 08:41:41 -05:00
AoElite 2d053d0923 update lunar's brand format in all the configs 2025-12-05 06:01:34 -05:00
AoElite 3bf3226fa5 make discord webhook pattern stricter and support different api versions 2025-11-29 06:13:36 -05:00
Nils Gereke bf637f5fb3 feat: discord forum channel support (#2380) 2025-11-28 22:25:53 -07:00
NotAlexNoyle 3ae1e32813 Downgrade gradle to 8.14.3, remove fabric support, update README.md 2025-11-28 09:25:26 -05:00
Axionize e2e25206ec Update packetevents 2025-11-27 17:38:23 -05:00
Axionize 8f3b7db844 Merge remote-tracking branch 'origin/2.0' into lightning
# Conflicts:
#	.github/workflows/build-and-publish.yml
#	.github/workflows/codeql-analysis.yml
#	bukkit/src/main/java/ac/grim/grimac/platform/bukkit/events/PistonEvent.java
2025-11-23 18:49:49 -05:00
Axionize 0d67045f66 Include build number on modrinth 2025-11-08 19:27:49 -05:00
Axionize 49742a9ddc Fix build increment conditional 2025-11-08 18:32:22 -05:00
Axionize 612e0f8f67 Fix permissions for increment build number creating tags/refs 2025-11-08 18:18:52 -05:00
Axionize 5d4af9365b Forget to make workflows run on lightning branch 2025-11-08 17:41:56 -05:00
Axionize 5489f118c1 Fix buildscripts again 2025-11-08 17:40:38 -05:00
Axionize ded5c0f320 Restructure to help upstream forks.
People have started building forks of LightningGrim...

I never intended for this but regardless to help them I'll stop rebasing and use merges only.
2025-11-08 17:37:59 -05:00
Axionize 238b77c002 Fix buildscripts 2025-11-08 17:18:09 -05:00
Axionize 71d93c97cf Minor build fixes after rebase on upstream 2025-11-08 17:12:49 -05:00
Axionize 43f4c7fcd7 README updates for LightningGrim 2025-11-08 17:12:49 -05:00
Axionize 03714c9b97 Fix compile issues from rebasing upstream 2025-11-08 17:12:49 -05:00
Axionize 004804c09e Potentially fix LightningGrim-specific vector culling Reach False? 2025-11-08 17:12:49 -05:00
Axionize 853d02b9c8 Temp stash 2025-11-08 17:12:49 -05:00
Axionize 2272d4df16 Optimize Bukkit PistonEvent() usage 2025-11-08 17:12:49 -05:00
Axionize fa2dc7b00b * Remove space in names of new checks:
* "Wall Hit" -> "WallHit"
    * "Entity Pierce" -> "EntityPierce"
* Fix modeling experience orb as unhittable entity
* Fix sending hitbox with /grim hitboxdebug in the hitboxdebug handler
* Update upstream to 35811fe551
2025-11-08 17:12:49 -05:00
Axionize c8347ed146 Heavy memory use optimization
- 2.2x reduction in queued task overhead
- reduction in size of queued single block changes
- up to 8x reduction in size of queued multi block changes
    - fixed blocks disappearing in B73 caused by incorrect shifting in new block udpate handler
2025-11-08 17:12:49 -05:00
Axionize c94260bea6 Buildscript Update & Dirty Painting Hack
- Update gradle buildscript to not include branch-name if branch is called "lightning"
 - Update GitHub Actions to automatically include changelog and to rename the jar LightningGrim-${platform}-${version}.jar
 - Mark Paintings as entities that cannot be hit to prevent EntityPierce (formally HitboxEntity) falses.
2025-11-08 17:12:48 -05:00
Axionize bde2328511 Update buildscript
Notes:
- Mark actions builds automatically as alpha on Modrinth
- Shorten Fabric version string to be same as Bukkit on Modrinth
2025-11-08 17:12:48 -05:00
Axionize b87eeaf490 Cull dead entities from canHit() 2025-11-08 17:12:48 -05:00
Axionize 3abc904590 Add support for Hitbox Debugging 2025-11-08 17:12:48 -05:00
Axionize 1b416fc841 - Re-add HitboxBlock and HitboxEntity (renamed to WallHit and EntityPierce)
- Reduce unnecessary reach vectors used for calculations in versions
2025-11-08 17:12:48 -05:00
Axionize 224f464eff Add Inventory checks 2025-11-08 17:12:48 -05:00
Axionize ac35735fff Runtime startup check of PE version 2025-11-08 17:12:48 -05:00
tjshtqwq f54ca0e231 Update zh.yml 2025-09-06 17:11:33 +08:00
tjshtqwq 75b5405160 Merge branch '2.0' into patch-1 2025-09-06 16:59:44 +08:00
tjshtqwq c2acfae296 Update translation 2025-09-05 22:43:31 +08:00
378 changed files with 6997 additions and 7130 deletions

No files matched your search

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