114 Commits
Author SHA1 Message Date
Lysandr0 a64a911486 Update poms
And a small cleanup
2026-11-06 19:17:05 +01:00
Xaw3ep 15ae1001ee Merge pull request #448 from Updated-NoCheatPlus/transposable
SurvivalFly 'design change
2026-11-06 15:46:52 +07:00
Xaw3ep 1ee3c1ff5e One more fine tune
Hidden motion recovery should no longer run after 1.18.2 although till ~1.21.2 has the real fix. From 1.18.2, the gap is small enough to be always sent
Align entities data from normal events and from packets
2026-11-06 15:40:41 +07:00
Xaw3ep a801091438 A bit refactoring
Allow operation logics can be swapped easily across versions.
Should behave the same before this commit although HiddenMotionReconstructor was not tested

Gliding changes allow boost to end both 1 tick late or immediately.
2026-11-05 01:40:58 +07:00
Xaw3ep 02abcfc1c9 Small tweaks
Correct firework ticking and correct hitbox on some blocks. That it.
2026-11-04 21:45:35 +07:00
Lysandr0 9ce8772fcf Add MC 26.3 blocks.
Not tested. Needs handling for the shelf mushroom bounce mechanic.
2026-10-28 21:04:38 +01:00
Xaw3ep 75ecd05ff3 Add basic velocity handler and entity tracker
Not tested much. Somewhere here and there left unoptimized or incompatible across versions.
2026-10-27 21:14:58 +07:00
Xaw3ep c67e4a4808 Fixes broken headObstructed detection 2026-05-29 23:09:39 +07:00
Xaw3ep 524a48dec3 Less voodoo on NoFall
Fixes fall damage is incorrectly dealt.
We can properly operate in non deal-damage mode when with ProtocolLib by later altering ground state.
2026-05-29 14:33:10 +07:00
Xaw3ep c96fdbb5aa Fixes stair and slime 2026-05-22 20:04:20 +07:00
Xaw3ep ee3078aafa Ops 2026-05-20 17:21:15 +07:00
Xaw3ep 6f6a1e717a Temporary fix for edge sneaking & clean up 2026-05-20 15:03:23 +07:00
Xaw3ep c8b3b14999 Merge pull request #440 from Inquisitors-transfers/bedrock-folia-compat-no-java-models
Compatibility with Folia, Start touching Bedrock a little bit
2026-05-18 17:30:55 +07:00
Git a10f890344 Add Bedrock-only SurvivalFly support envelopes 2026-05-14 23:20:43 -04:00
Git a5c174672a Strip Java movement model leftovers 2026-05-14 23:05:51 -04:00
Git ed210aea3f Split block environment compatibility foundations 2026-05-14 21:59:31 -04:00
Git 09a751eecc Split Folia and net synchronization fixes 2026-05-14 21:56:49 -04:00
Git 5ed24b6545 Split diagnostics config and documentation 2026-05-14 21:53:22 -04:00
Xaw3ep 4ecbdffa07 Start fixing false positives
Since was only tested on 1.20.6, so there might changes across versions might miss
2026-05-13 20:12:26 +07:00
Lysandr0 13a65cb9c4 Formatting, comments, rename some flags, add new MC 26 blocks. 2026-05-09 14:33:27 +02:00
Xaw3ep 9aef9c61cd Maybe final touch with split moves
Slight optimize split moves, do vertical hidden motion restoration too.
Upcoming commits will fix false positives
2026-04-29 14:27:12 +07:00
Lysandr0 46958110d2 Bump NCP to MC 26, Get rid of the legacy shape patch framework, comments/documentation, reduce memory footprint by keeping track of only 2 past moves.
More: get rid of legacy and commented out code (BlockProperties, BlockFlags, AxisAlignedBBUtils, MoveData); deprecate the legacy F_GROUND_HEIGHT flag; add golden dandelion flower; rename MaterialUtil.FULLY_PASSABLE_BLOCKS to NO_COLLISION_BOX
2026-04-05 15:25:48 +02:00
Xaw3ep 1dbae29c4b Tiny fix/cleanup, sync with local
Before I mess things up again
2026-03-29 01:06:49 +07:00
Lysandr0 4e4998e8ae Ops. 2026-03-27 17:15:40 +01:00
Lysandr0 991946290e Let some library methods use the block cache. 2026-03-27 17:07:01 +01:00
Xaw3ep 70a6e6d7ad Restore hidden moves
A prototype so might not fully efficient at some point:
+ Yaw is unknown, assumption was made that unchanged during hidden moves
+ Make sure vertical AND horizontal not larger than threshold
+ Haven't done for vertical
2026-03-23 08:27:10 +07:00
Lysandr0 5a5f20b459 Add copper torch model, don't wildcard lost ground with elytras, comments, throw in Optifine's trigonometric tables 2026-03-01 21:00:49 +01:00
Lysandr0 ddb128dbe8 Merge pull request #424 from Phoenix616/pr/fix-spears
Use correct itemInUse method (Fixes #423)
2026-02-06 08:32:28 +01:00
Xaw3ep f90a7c1b76 Fix exceptions
Fixed #421
Fixed #422
2026-02-04 23:24:01 +07:00
Phoenix616 cedb9710a0 Use correct itemInUse method (Fixes #423)
Also adjust documentation so the correct BridgeMisc method is used
 and prevent potential NPE in BridgeMisc#getItemInUse
2026-02-02 16:36:31 +01:00
Lysandr0 9048ea023b Refine shulker box shape.
More of a test than anything.
The shape is still "wrong" as the box is progressively increased vertically or horizontally (up to +0.5), when players open it. It is not instantly increased by 0.5 when opened.
2026-02-01 11:26:53 +01:00
Lysandr0 3ea3829ae9 Fix issues with block-breaking/shape-definition for 1.21.9 blocks. 2026-01-25 22:41:35 +01:00
Xaw3ep 20e5a7164e An attempt to fix build 2026-01-21 00:00:00 +07:00
Xaw3ep d886176eff Split bounding box for collision and visible check
Finally done the side quest! Add latency handler with overall stabilize should done most of rework
2026-01-20 23:24:20 +07:00
Lysandr0 402b4fd5c5 [blind] Add shelf model 2026-01-11 15:02:16 +01:00
Lysandr0 959f8151ab Apply stash@{0} from branch 1.21.11-update (recovered stash) 2026-01-10 11:05:30 +01:00
Xaw3ep a35d8ffe61 Fix ABI compatibility 2026-01-08 11:28:34 +07:00
Xaw3ep 5e6ee4b8cc Resolve collision shape for few last complex blocks 2026-01-08 04:49:12 +07:00
Xaw3ep 3601db801a Merge pull request #416 from Phoenix616/pr/copper-tools
Support copper tools & 1.21.11 ClientVersion
2026-01-07 20:55:07 +07:00
Phoenix616 066d0268a3 Bump ClientVersion to 1.21.11 2026-01-06 18:57:57 +01:00
Phoenix616 e469c6727c Support copper tools 2026-01-06 18:55:42 +01:00
Xaw3ep 3c54300831 Fix powder snow & correct some blocks' shape
* More or less preparation for splitting out using interaction shape and collision shape instead using collision for both.
* Also because quite hard to add NCP's shape inset exclude/include into RichEntityLocation#collide. Correct the shape, no more mid-way manipulation
* Still there fences, walls, glass panes, bars to go
* Visible checks might malfunction with changed blocks
* Cross-version blocks haven't been tested much from 1.5-1.12.2 as too many false positives from SurvivalFly
2025-12-20 21:29:17 +07:00
Lysandr0 042113e978 Comments, formatting and documentation…
Also, in SurvivalFly, fix minor oversight with the supporting block data passing yAllowedDistance instead of the actual yDistance as argument (horizontal) + check if a legacy player can actually climb upwards.
2025-12-14 21:46:16 +01:00
Lysandr0 2d021ded3a Bump ClientVersion to 1.21.9 2025-12-14 21:44:14 +01:00
Lysandr0 2e6babb977 Adjustments for levitation magic workarounds. 2025-12-14 21:43:04 +01:00
Lysandr0 369f190116 Be sure to set data on early return of movement processing.
This data is needed for powder snow's shape definition. As such, it needs to always be set.
Ideally, the shape should be independent from data set by checks… Needs some refactoring.
2025-12-14 21:42:34 +01:00
Lysandr0 7ef148301e Having a look at inventory-related stuff…
* Skip inv-move checking if moreInv already returned true.
* Do make use of the newer Bukkit API in Open for the invMove check: if the player#getCurrentInput() method isn't available, fall back to NCP's predicted movement.
This makes the check dependent on data set in SurvivalFly but I don't seen any other solution.
* Comment in fight-listener + balance Improbable weights out.
2025-12-14 21:39:32 +01:00
Lysandr0 1449067c17 Merge pull request #414 from Phoenix616/pr/fix-npe
Fix possible NPE and clean up more edge interaction checks (Fixes #409)
2025-12-13 21:01:35 +01:00
Phoenix616 127fbf725c Fix possible NPE and clean up more edge interaction checks (Fixes #409) 2025-12-11 23:57:50 +01:00
Xaw3ep 25b9d27646 First attempt to handle ground riptide
Also fix an error with old versions
2025-12-02 17:36:17 +07:00
Lysandr0 3198615873 Fix riptide-equipped trident release not being detected; fix elytra + riptide; attempt to fix illegalsprint false positives.
* Order of operations for gliding with an elytra while riptiding was wrong: when gliding, riptide motion is added before. Only question that remains is: before stuck speed or after?

* For the tridentRelease flag, the moving trace was resetting the flag too early: as a fix, simply reset the flag manually instead of in the resetBase.

* Some notes/comments
2025-11-08 10:32:25 +01:00
Lysandr0 125a23971a Update some comments and Javadocs
Remove references to ViaVersion's input emulation. It just tries to guess which input the player is moving with; totally unreliable.
2025-11-04 10:59:13 +01:00
Xaw3ep eff2d83139 Fix random liquid false positives 2025-11-04 16:42:04 +07:00
Lysandr0 e5df35dcce Adjustments for the vertical prediction (liquids) 2025-11-03 09:52:25 +01:00
Xaw3ep 5987b4d381 Another sound fix 2025-11-02 18:11:13 +07:00
Lysandr0 1beb1d188f Ops. 2025-11-02 11:37:49 +01:00
Lysandr0 6c1f3caf41 Some touch ups.
Change Vector parameters to BlockCoord ones in SupportingBlockUtils, as we're working with block positions here, not arbitrary Vectors

* Cleanup unused/mostly useless methods in RichBounds

* Avoid checking for a second collision in the "isSupportedBy" method (RichEntity)

* Don't set impulses to NONE in processGliding (SurvivalFly)

* Some cleanup for CreativeFly, MagicWorkarounds
2025-11-02 11:26:33 +01:00
Xaw3ep c8850e7b83 Now working supporting block 2025-11-01 22:25:14 +07:00
Xaw3ep dcdfbc83e0 [Deficient] Make supporting block works 2025-10-21 01:10:18 +07:00
Lysandr0 c4ab7560a4 Don't wildcard lost ground when checking for vertical collisions.
Only check for lost from-on-ground positions.

Do note that the dispatched "fromOnGround" argument takes into account block changes.
2025-10-19 11:13:26 +02:00
Lysandr0 5f26653c7f Fix yOnGround parameter for fastbreak; revise block push handling for SurvivalFly.
The ground-seeking parameter was abnormally large, and no information or documentation was left for us about it. However, it does seem to cause false positives when breaking blocks below, hence the fix.
We now just use the normal yOnGround parameter for movement checks.

Block push is now prioritized in predictions: the vertical one is skipped altogether, and the movement is simply allowed.
For the horizontal one, it’s a bit trickier: the prediction is skipped only if the push occurred on both axes at the same time, saving some resources. If the push occurred on only one axis, the prediction still runs for both, but the result is overridden for the axis on which the push occurred.
This is inefficient performance-wise; we’ll need a refactor to allow NCP to skip predictions on a single axis only.
2025-10-19 11:06:29 +02:00
Lysandr0 112bdaa043 More cleanup.
* Remove several unused methods in PhysicsEnvelope.
* Comments here and there to further clarify code.
* Remove left-over elytra strict mode for CreativeFly.
* Remove useless Magic values,
2025-10-19 11:00:08 +02:00
Lysandr0 108435fd86 Get rid of the void-to-void set back policy.
Questionable policy.
It was added by asofold as a very quick way of stopping players from abusing NCP's set back logic in specific game modes like SkyWars and SkyBlock.
NCP would/does set players back to their last valid ground position, which can be abused to not fall down into the void (though fall damage is still enforced).

We need to implement a "drag-down-to-ground" policy for this.
2025-10-13 19:26:06 +02:00
Xaw3ep 249da9c6e7 Tiny dig down fix
Left some random notes
2025-10-13 23:38:18 +07:00
Lysandr0 3817d568c6 Fix several false positives and bugs.
* Fix gravity being 0.0 at login.
* Re-add the Variable flag to pointed dripstone, as it seems needed for jumping on stacked dripstone for the ground judgement.
* Fix docs.
* Fix vdistrel and block-breaking (not definitive fix)
* Apply levitation workarounds from the last tick.
* Use the Bukkit-provided fall distance instead of ours in powder snow workarounds.
2025-10-12 09:59:11 +02:00
Lysandr0 a0e20f9c29 Fix levitation
Forgot to set the flag. Ops.
2025-10-11 16:29:53 +02:00
Lysandr0 91aa3a9f33 Clarify InputState's scope.
Renamed the class (once again) to better represent the scope of the class, which is storing data for key presses and releases NOT the current status of the player (which you can infer from the player's keyboard input)
2025-10-10 11:19:43 +02:00
Xaw3ep 7b1f19fc4b Set ClientVersion without ProtocolLib 2025-10-10 13:44:09 +07:00
Xaw3ep d84ea4dc92 Random infrastructure fixing
*Changes:
- Fixed infinite VLs for 1.5-1.6.4 lower with wrong attributes values
- Fixed some BukkitAPI problem with sound
- Handled properly Input listener on versions that not-exist such class
- Covered all NPE problems related to usage of PlayerData for block definition. If it null, a check is calling without setting it assume as latest server definition
- Removed safeguard for ClientVersion. It's our fault not setting version correctly not the lib. Also to tell that player is on very old versions
2025-10-10 11:58:21 +07:00
Lysandr0 1f0f9c972c Fix NPE with the Visible checks. 2025-10-09 15:53:41 +02:00
Lysandr0 c09af5be3b Add getLastStrafe(), get rid of sfDirty flag. 2025-10-09 12:22:22 +02:00
Lysandr0 4c86d0a8bf Fix several aspects for snow and powder snow collision.
Also, do set the client version before CNCP. If the setup is correct, CNCP will override what NCP has set.
2025-10-09 10:49:27 +02:00
Lysandr0 8f904387e2 Fix client version setting. 2025-10-09 09:35:19 +02:00
Xaw3ep e474df86f4 Fine
If failed one more time, won't rush
2025-10-08 23:38:05 +07:00
Xaw3ep 3703a51529 Oops 2025-10-08 23:34:23 +07:00
Xaw3ep c08cec4f9b Fixed 1.21.2 input 2025-10-08 23:21:18 +07:00
Lysandr0 da1058c7e4 Update ClientVersion to the latest protocol ID. 2025-10-08 18:05:43 +02:00
Lysandr0 c398a4988e Ops :) 2025-10-08 17:38:59 +02:00
Lysandr0 dcda38716b Refactor for InputState.
Later, we should merge input tracking fields in CombinedData with InputState
2025-10-08 17:36:32 +02:00
Lysandr0 5eafb1f506 Catch even more (literal) edge cases with lost ground. 2025-10-07 17:03:39 +02:00
Lysandr0 df4f2079b4 Fix several aspects of vdistrel; rename class; remove moving trace flags;
For lost ground, we know only (and always) check for the "from" position, since it is the one we're interested in, when checking if the momentum initialisation should be set to 0.0. The "to" position is irrelevant and should only be used to further confine workarounds and such.
As a result, the lost ground brute force has been removed from both predictions.
We require last y motion to be explicitly negative in such case.

The 0.2 motion ovveride was in the wrong place, so it should now be correct, however for lower versions (1.21.1-), we need to loop the space bar press.

Removed several flags from PlayerMoveData in favour of more simple flags in CombinedData, as we don't need to track more than the last state for these specific effects/status. Also, from 1.21.2 and onwards, the input data should be used to read sprinting and sneaking.

Adjusted lost ground conditions a bit (horizontal motion requirements, y acceleration condition)

Renamed InputDirection to InputState, as we now track more than just WASD impulses.

Fixed an NPE in SupportingBlockUtils.
2025-10-07 09:42:20 +02:00
Xaw3ep 264302636c Minor fixes 2025-10-01 21:55:50 +07:00
Lysandr0 ade49a577d Attempt to fix hidden micro moves, revert order of negligible momentum checking.
When the player comes to a stop, after several micro (and split) moves, the actual stop isn't sent to the server. The next move will yield a false positive due to the player moving off with motion based on a previous move of 0.0, which the client did not send.
There doesn't seem to be a way to detect this gap in movement, thus this brute-force case.

Bonus:
* Cleanup
* Comments
* Simplify sprinting checks.
2025-09-30 23:59:23 +02:00
Lysandr0 3e8566de2a Get rid of munchhausen.
The check has been irrelevant for a very long time, since asofold reworked the vertical handling with vDistRel.
It was coded as a preemptive measure for potential bypasses, back when NCP's vertical check was based on jump phases counting (more than a decade ago).
The fix was also questionable at best, as it basically interfered with a vanilla feature (hitting oneself with a rod).
2025-09-28 11:39:50 +02:00
Lysandr0 8bf59cd85b Misc. changes.
* Review improbable checking for fastplace.
* Several comments.
* Rename methods in PlayerData.
* Alter several listener in CombinedListener to be at MONITOR level.
* Request Improbable checking on sending an invalid flying packet.
* Get rid of legacy bloat (Sflegacy)
* SurvivalFly:
  - Attempt to reduce brute-forcing with the horizontal prediction by re-checking for lost ground only when a "from" case happens.
  - Re-order vertical negligible speed reset.
2025-09-26 17:18:50 +02:00
Xaw3ep 2839e6a40f Forgot that hspeed consumes Input data when use. Cloning!
Also fixed most weird hspeed falses due to wrong place for negligible speed.
2025-09-26 07:51:28 +07:00
Xaw3ep f2a8cc6f64 Correct collision block shapes, versions crossing compatible 2025-09-24 18:19:37 +07:00
Xaw3ep e3560651c1 Proper Input Implementation
And some random notes
2025-09-23 02:56:44 +07:00
Lysandr0 aa08284c03 Let it compile. 2025-09-15 18:03:40 +02:00
Lysandr0 60fa3bbdc7 Several changes.
* Attempt to model the correct/exact collision shape of Scaffolding. To be tested.
* Begin to lay out the groundwork for a flying packet-move event sync system.
* Get rid of the old vDistClimbable implementation, now that we have all shapes for climbables.
* Lots of cleanup here and there.
* Missing headers.
* Do request a set-back for ALL cancelled flying packets; even those awaiting for an ACK packet.
* Listen for RiptideEvent; not complete.
* Feed couldstep usage to lost ground (very low weight but might prevent repeated abuse).
* Remove unused variables from netdata.
* Refine trident velocity calculation to account for the 1.2 offset properly.
2025-09-15 15:40:14 +02:00
Lysandr0 7a8995cb2b Merge pull request #403 from Phoenix616/pr/hide-debug-messages-unless-enabled
Only print debug chat messages when debugging is enabled (#402)
2025-09-10 17:24:00 +02:00
Phoenix616 5a7a6cc76b Fix build by updating address of ProtocolLib repository 2025-09-10 12:36:07 +02:00
Phoenix616 91692d8a5e Only print debug chat messages when debugging is enabled (Fixes #402) 2025-09-10 12:26:16 +02:00
Xaw3ep 0ec5bd9439 Fix vertical prediction not working
Accidentally set vertical prediction on fire because of changing internal return value type but forgot to change outside calls. Should be fixed.

* Up coming(depend on priority and difficulty of doing):
- Complete Input tracking system
- Further refine isOnGround() shortcut and workarounds
- Add wall collision handler / affirmation of velocity usage
- Add minimal entities tracker to improve entity collision check
2025-09-06 10:10:08 +07:00
Lysandr0 a635fccd77 Update poms, fix drip leaf shape definition, review usage of the legacy F_VARIABLE flag, adjustments for powder snow's on-ground judgement.
Also add comments here and there.
Next up:
* Finish the Input tracking system.
* Fix vertical prediction not working, somehow.
2025-09-04 21:06:32 +02:00
Lysandr0 048ac8f84a [Bleeding][Broken] Sketch input-tracking on packet-level. 2025-09-02 17:45:26 +02:00
Lysandr0 02766a11d3 [Bleeding] Update reflection methods and fix bad version input error in GenericVersion. 2025-09-01 12:10:37 +02:00
Lysandr0 0623d7592f [Bleeding] Listen for impulses via PlayerInputEvent 2025-08-31 22:03:25 +02:00
Lysandr0 7e5c65b828 [Bleeding] Add 1.21.5/6 blocks.
Probably needs adjustments; especially for the hit/collision box definition of wildflowers and leaf litter.
2025-08-31 12:19:31 +02:00
Lysandr0 e14a49e6e3 Let it compile 2025-08-30 11:06:07 +02:00
Lysandr0 5b3bda5cb8 Merge branch 'dev' 2025-08-17 12:20:04 +02:00
Lysandr0 560ff53bac Moving random/small changes on remote. 2025-08-17 12:17:40 +02:00
Lysandr0 6a13a6b4ed Merge pull request #378 from TreemanKing/1214
fix: 1.21.3 new packet data for position
2025-03-18 10:39:37 +01:00
Lysandr0 ba2e189b8d Merge pull request #383 from PsychoHashMap/patch-1
Added better ProtocolLib compatibility for later versions.
2025-03-18 10:36:09 +01:00
PsychoHashMap 67dd4bccda Added better ProtocolLib compatibility for later versions.
Tested as working on 1.21.1.
2025-03-07 19:02:02 +00:00
TreemanK 2ac8c27856 fix: 1.21.3 new packet data for position
HorizontalCollision was added as a new boolean. This caters for it
2024-12-13 12:28:20 +11:00
Lysandr0 53ff9d299d Revert the "noslow bypass" fix for the moment. 2024-10-14 16:38:23 +02:00
Lysandr0 53d14f3a36 Merge pull request #372 from IAISI/master
Minor performance improvements
2024-09-15 08:59:13 +02:00
Dieu d4181ce990 Minor performance improvements 2024-09-10 00:40:14 +02:00
Lysandr0 38d0486279 Merge pull request #369 from EclipsesDev/fix-noslow
Fixed updatedncp switchitem bypass
2024-09-04 10:14:28 +02:00
Eclipses 6143288a88 Fixed updatedncp switchitem bypass
previous one fixes switchitem noslow if it is send constantly, but if it send once + c08 packet, previous fix will not work, so this one should patch it
2024-08-30 21:41:38 +07:00
Lysandr0 dd2136eea7 Merge pull request #367 from skbeh/fix-below-1-12-loading
Fix loading on below 1.12
2024-08-22 09:15:57 +02:00
skbeh a2c81f7d9b Fix loading on below 1.12 2024-08-20 10:04:38 +00:00
Lysandr0 e4126cdfa8 Heavy core assigned to pickaxe. 2024-08-07 14:45:05 +02:00
221 changed files with 16023 additions and 4521 deletions

No files matched your search

+6 -2
View File
@@ -6,13 +6,17 @@ labels: ''
assignees: '' assignees: ''
--- ---
#### *Complete* output of the `ncp version` command (please prefer text here) #### *Complete* output of the `ncp version` command
#### Short description of the issue and how to reproduce (is it random / always happens / side conditions ?) ### Output of the `ncp inspect [playername]` command for in-game issues
#### Short description of the issue and how to reproduce
#### Have you made any (noteworthy) changes to the default configuration of NCP? #### Have you made any (noteworthy) changes to the default configuration of NCP?
#### Do you run any other special plugins that affect game mechanics next to NC+ (skills, machines, adding/changing blocks, other anti-cheating)? #### Do you run any other special plugins that affect game mechanics next to NC+ (skills, machines, adding/changing blocks, other anti-cheating)?
#### Do you use **client-side** mods that could interfere with vanilla game mechanics **_(such as Optifine's FastMath)_**?
#### Provide additional information: for bypasses, vulnerabilities and reproducible issues, we request a [debug log. ](https://github.com/Updated-NoCheatPlus/Docs/blob/master/Development/Debugging.md#on-the-fly-debug-output-for-individual-players) Use [GitHub gists](https://gist.github.com/) or [PasteBin](http://pastebin.com/) or similar for errors and huge logs, or attach a (zip-) file here. #### Provide additional information: for bypasses, vulnerabilities and reproducible issues, we request a [debug log. ](https://github.com/Updated-NoCheatPlus/Docs/blob/master/Development/Debugging.md#on-the-fly-debug-output-for-individual-players) Use [GitHub gists](https://gist.github.com/) or [PasteBin](http://pastebin.com/) or similar for errors and huge logs, or attach a (zip-) file here.
@@ -18,6 +18,7 @@ import java.util.Collection;
import fr.neatmonster.nocheatplus.utilities.ds.bktree.SimpleTimedBKLevenshtein.STBKLResult; import fr.neatmonster.nocheatplus.utilities.ds.bktree.SimpleTimedBKLevenshtein.STBKLResult;
import fr.neatmonster.nocheatplus.utilities.ds.bktree.TimedBKLevenshtein.SimpleTimedLevenNode; import fr.neatmonster.nocheatplus.utilities.ds.bktree.TimedBKLevenshtein.SimpleTimedLevenNode;
import fr.neatmonster.nocheatplus.utilities.ds.bktree.BKModTree.LookupEntry;
public class SimpleTimedBKLevenshtein extends TimedBKLevenshtein<SimpleTimedLevenNode, STBKLResult> { public class SimpleTimedBKLevenshtein extends TimedBKLevenshtein<SimpleTimedLevenNode, STBKLResult> {
@@ -16,10 +16,20 @@ package fr.neatmonster.nocheatplus.utilities.ds.map;
import fr.neatmonster.nocheatplus.utilities.Misc; import fr.neatmonster.nocheatplus.utilities.Misc;
public class BlockCoord { /**
private final int x; * Integer block coordinate with support for in-place mutation.
private final int y; */
private final int z; public class BlockCoord implements Cloneable {
/*
* NOTE: This class is now mutable to allow in-place addition/subtraction
* operations that do not create new instances. Because instances are
* mutable they are NOT safe to use as keys in hash-based collections if
* their coordinates may change while stored; use {@link #copy()} to get an
* immutable snapshot when needed.
*/
private int x;
private int y;
private int z;
/** /**
* Constructs a BlockCoord with integer coordinates. * Constructs a BlockCoord with integer coordinates.
@@ -72,4 +82,115 @@ public class BlockCoord {
BlockCoord bc = (BlockCoord) obj; BlockCoord bc = (BlockCoord) obj;
return bc.getX() == x && bc.getY() == y && bc.getZ() == z; return bc.getX() == x && bc.getY() == y && bc.getZ() == z;
} }
/**
* Add the given BlockCoord to this one.
*
* @param other the other BlockCoord to add to this one
* @return this (after modification)
*/
public BlockCoord add(BlockCoord other) {
return add(other.x, other.y, other.z);
}
/**
* Add the given integers to this BlockCoord.
*
* @param dx delta x
* @param dy delta y
* @param dz delta z
* @return this (after modification)
*/
public BlockCoord add(int dx, int dy, int dz) {
this.x += dx;
this.y += dy;
this.z += dz;
return this;
}
/**
* Subtract the given BlockCoord from this one.
*
* @param other the other BlockCoord to subtract
* @return this (after modification)
*/
public BlockCoord subtract(BlockCoord other) {
return subtract(other.x, other.y, other.z);
}
/**
* Subtract the given integers from this BlockCoord.
*
* @param dx delta x
* @param dy delta y
* @param dz delta z
* @return this (after modification)
*/
public BlockCoord subtract(int dx, int dy, int dz) {
this.x -= dx;
this.y -= dy;
this.z -= dz;
return this;
}
/**
* Returns the squared Euclidean distance between this block coordinate
* and another BlockCoord (integer arithmetic). Returns a double to match
* common expectations and avoid overflow for large distances.
*
* @param other the other block coordinate
* @return squared distance as a double
*/
public double distanceSquared(BlockCoord other) {
long dx = this.x - other.x;
long dy = this.y - other.y;
long dz = this.z - other.z;
return dx * dx + dy * dy + dz * dz;
}
/**
* Returns the squared Euclidean distance between this block coordinate
* and the specified integer coordinates.
*
* @param x x coordinate to compare to
* @param y y coordinate to compare to
* @param z z coordinate to compare to
* @return squared distance as a double
*/
public double distanceSquared(int x, int y, int z) {
long dx = this.x - x;
long dy = this.y - y;
long dz = this.z - z;
return dx * dx + dy * dy + dz * dz;
}
/**
* Create a copy of this BlockCoord. Returns a new independent instance
* with the same coordinates (useful when you need an immutable snapshot
* while mutating the original).
*
* @return a new BlockCoord with identical coordinates
*/
public BlockCoord copy() {
return new BlockCoord(x, y, z);
}
/**
* Clone this BlockCoord.
* <p>
* This implementation attempts to use Object#clone() (via
* super.clone()). If, for some reason,
* cloning via super is not supported (CloneNotSupportedException), we
* fall back to creating a manual copy via {@link #copy()}.
*
* @return a new BlockCoord with identical coordinates
*/
@Override
public BlockCoord clone() {
try {
return (BlockCoord) super.clone();
} catch (CloneNotSupportedException e) {
return copy();
}
}
} }
@@ -16,6 +16,7 @@ package fr.neatmonster.nocheatplus.utilities.ds.prefixtree;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.CharPrefixTree.SimpleCharNode; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.CharPrefixTree.SimpleCharNode;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree.SimpleCharLookupEntry; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree.SimpleCharLookupEntry;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.CharPrefixTree.CharLookupEntry;
/** /**
* Adding some convenience methods. * Adding some convenience methods.
@@ -16,6 +16,7 @@ package fr.neatmonster.nocheatplus.utilities.ds.prefixtree;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.PrefixTree.SimpleNode; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.PrefixTree.SimpleNode;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimplePrefixTree.SimpleLookupEntry; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimplePrefixTree.SimpleLookupEntry;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.PrefixTree.LookupEntry;
/** /**
* Simple prefix tree, for one given key type. * Simple prefix tree, for one given key type.
@@ -16,6 +16,7 @@ package fr.neatmonster.nocheatplus.utilities.ds.prefixtree;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.TimedCharPrefixTree.SimpleTimedCharNode; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.TimedCharPrefixTree.SimpleTimedCharNode;
import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.TimedCharPrefixTree.TimedCharLookupEntry;
public class SimpleTimedCharPrefixTree extends TimedCharPrefixTree<SimpleTimedCharNode, SimpleTimedCharLookupEntry> { public class SimpleTimedCharPrefixTree extends TimedCharPrefixTree<SimpleTimedCharNode, SimpleTimedCharLookupEntry> {
+2 -1
View File
@@ -19,7 +19,8 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version> <!-- Do not increase. 1.12 was the last version having its own native module -->
<version>1.21.11-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -14,6 +14,10 @@
*/ */
package fr.neatmonster.nocheatplus.compat.bukkit; package fr.neatmonster.nocheatplus.compat.bukkit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
@@ -21,6 +25,8 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.compat.SchedulerHelper;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
import fr.neatmonster.nocheatplus.utilities.map.BlockFlags; import fr.neatmonster.nocheatplus.utilities.map.BlockFlags;
import fr.neatmonster.nocheatplus.utilities.map.BlockProperties; import fr.neatmonster.nocheatplus.utilities.map.BlockProperties;
@@ -28,6 +34,12 @@ import fr.neatmonster.nocheatplus.utilities.map.MaterialUtil;
public class BlockCacheBukkit extends BlockCache { public class BlockCacheBukkit extends BlockCache {
private static final long FOLIA_FALLBACK_LOG_INTERVAL_MS = 10000L;
private static final int FOLIA_FALLBACK_CONSOLE_LOG_MIN_COUNT = 2;
private static final AtomicInteger foliaFallbackCount = new AtomicInteger();
private static final AtomicInteger foliaFallbackResolvedCount = new AtomicInteger();
private static final AtomicLong nextFoliaFallbackLog = new AtomicLong();
protected World world; protected World world;
/** Temporary use. Use LocUtil.clone before passing on. Call setWorld(null) after use. */ /** Temporary use. Use LocUtil.clone before passing on. Call setWorld(null) after use. */
@@ -50,21 +62,97 @@ public class BlockCacheBukkit extends BlockCache {
@Override @Override
public Material fetchTypeId(final int x, final int y, final int z) { public Material fetchTypeId(final int x, final int y, final int z) {
// TODO: consider setting type id and data at once. // TODO: consider setting type id and data at once.
return world.getBlockAt(x, y, z).getType(); if (world == null) {
return Material.AIR;
}
if (SchedulerHelper.isOwnedByCurrentRegion(world, x, z)) {
return world.getBlockAt(x, y, z).getType();
}
final Material fallbackType = fetchTypeIdWithLocationFallback(x, y, z);
return fallbackType == null ? Material.AIR : fallbackType;
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public int fetchData(final int x, final int y, final int z) { public int fetchData(final int x, final int y, final int z) {
// TODO: consider setting type id and data at once. // TODO: consider setting type id and data at once.
return Bridge1_13.hasIsSwimming() ? 0 : world.getBlockAt(x, y, z).getData(); if (world == null) {
return 0;
}
if (SchedulerHelper.isOwnedByCurrentRegion(world, x, z)) {
return Bridge1_13.hasIsSwimming() ? 0 : world.getBlockAt(x, y, z).getData();
}
final Integer fallbackData = fetchDataWithLocationFallback(x, y, z);
return fallbackData == null ? 0 : fallbackData.intValue();
}
private Material fetchTypeIdWithLocationFallback(final int x, final int y, final int z) {
// Folia compatibility: if the chunk-level ownership check cannot prove safety, retry the exact location.
// If that also fails, keep AIR as the safe final fallback instead of touching another region's block state.
boolean resolved = false;
try {
if (SchedulerHelper.isOwnedByCurrentRegion(new Location(world, x, y, z))) {
resolved = true;
return world.getBlockAt(x, y, z).getType();
}
}
catch (Throwable t) {
// Keep the final AIR fallback for servers that throw while checking ownership or block state.
}
finally {
logFoliaBlockFallback("type", resolved);
}
return null;
}
@SuppressWarnings("deprecation")
private Integer fetchDataWithLocationFallback(final int x, final int y, final int z) {
// Folia compatibility: retry exact-location ownership before returning legacy data 0 as the final fallback.
boolean resolved = false;
try {
if (SchedulerHelper.isOwnedByCurrentRegion(new Location(world, x, y, z))) {
resolved = true;
return Integer.valueOf(Bridge1_13.hasIsSwimming() ? 0 : world.getBlockAt(x, y, z).getData());
}
}
catch (Throwable t) {
// Keep the final data 0 fallback for servers that throw while checking ownership or block state.
}
finally {
logFoliaBlockFallback("data", resolved);
}
return null;
}
private static void logFoliaBlockFallback(final String kind, final boolean resolved) {
if (!CheckUtils.shouldLogDebugToConsole()) {
return;
}
final int count = foliaFallbackCount.incrementAndGet();
if (resolved) {
foliaFallbackResolvedCount.incrementAndGet();
}
final long now = System.currentTimeMillis();
final long next = nextFoliaFallbackLog.get();
if (now < next || !nextFoliaFallbackLog.compareAndSet(next, now + FOLIA_FALLBACK_LOG_INTERVAL_MS)) {
return;
}
final int resolvedCount = foliaFallbackResolvedCount.getAndSet(0);
final int periodCount = foliaFallbackCount.getAndSet(0);
// Folia diagnostic: a single safe fallback during teleport/chunk handoff is expected and not useful console noise.
if (periodCount < FOLIA_FALLBACK_CONSOLE_LOG_MIN_COUNT) {
return;
}
Bukkit.getLogger().info("[NCP][Folia][BlockCache] safe " + kind + " block fallback used "
+ periodCount + " times in the last 10s, resolved=" + resolvedCount
+ ", finalSafeFallback=" + (periodCount - resolvedCount) + ".");
} }
@Override @Override
public double[] fetchBounds(final int x, final int y, final int z){ public double[] fetchBounds(final int x, final int y, final int z){
Material mat = getType(x, y, z); Material mat = getType(x, y, z);
long flags = BlockFlags.getBlockFlags(mat); long flags = BlockFlags.getBlockFlags(mat);
if (flags == BlockFlags.F_IGN_PASSABLE) { if (flags == BlockFlags.F_IGN_PASSABLE_CHECK) {
return null; return null;
} }
// TODO: Want to maintain a list with manual entries or at least half / full blocks ? // TODO: Want to maintain a list with manual entries or at least half / full blocks ?
@@ -74,9 +162,15 @@ public class BlockCacheBukkit extends BlockCache {
@Override @Override
public boolean standsOnEntity(final Entity entity, final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ){ public boolean standsOnEntity(final Entity entity, final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ){
if (!SchedulerHelper.isOwnedByCurrentRegion(entity)) {
return false;
}
try{ try{
// TODO: Probably check other ids too before doing this ? // TODO: Probably check other ids too before doing this ?
for (final Entity other : entity.getNearbyEntities(2.0, 2.0, 2.0)){ for (final Entity other : entity.getNearbyEntities(2.0, 2.0, 2.0)){
if (!SchedulerHelper.isOwnedByCurrentRegion(other)) {
continue;
}
final EntityType type = other.getType(); final EntityType type = other.getType();
if (!MaterialUtil.isBoat(type) && type != EntityType.SHULKER){ // && !(other instanceof Minecart)) if (!MaterialUtil.isBoat(type) && type != EntityType.SHULKER){ // && !(other instanceof Minecart))
continue; continue;
@@ -101,4 +195,26 @@ public class BlockCacheBukkit extends BlockCache {
super.cleanup(); super.cleanup();
world = null; world = null;
} }
@Override
public double[] fetchVisualBounds(int x, int y, int z) {
Material mat = getType(x, y, z);
long flags = BlockFlags.getBlockFlags(mat);
if (flags == BlockFlags.F_IGN_PASSABLE_CHECK) {
return null;
}
// TODO: Want to maintain a list with manual entries or at least half / full blocks ?
// Always return full bounds, needs extra adaption to BlockProperties (!).
return new double[]{0D, 0D, 0D, 1D, 1D, 1D};
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
return true;
}
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -16,12 +16,19 @@ package fr.neatmonster.nocheatplus.compat.bukkit;
import java.util.Map; import java.util.Map;
import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import fr.neatmonster.nocheatplus.compat.SchedulerHelper;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitShapeModel; import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitShapeModel;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockFlags;
import fr.neatmonster.nocheatplus.utilities.map.MaterialUtil; import fr.neatmonster.nocheatplus.utilities.map.MaterialUtil;
@@ -57,6 +64,37 @@ public class BlockCacheBukkitModern extends BlockCacheBukkit {
return super.fetchData(x, y, z); return super.fetchData(x, y, z);
} }
@Override
public long fetchExtendedData(final int x, final int y, final int z) {
if (world == null) {
return 0L;
}
// Folia support: only query live Bukkit block data from the owning region thread.
if (SchedulerHelper.isOwnedByCurrentRegion(world, x, z)) {
return fetchWaterloggedFlags(x, y, z);
}
try {
if (SchedulerHelper.isOwnedByCurrentRegion(new Location(world, x, y, z))) {
return fetchWaterloggedFlags(x, y, z);
}
} catch (Throwable ignored) {
// Keep the cache fallback conservative if the scheduler API is unavailable.
}
return 0L;
}
private long fetchWaterloggedFlags(final int x, final int y, final int z) {
final IPlayerData pData = getPlayerData();
if (pData != null && pData.getClientVersion().isLowerThan(ClientVersion.V_1_13)) {
return 0L;
}
final BlockData data = world.getBlockAt(x, y, z).getBlockData();
if (data instanceof Waterlogged && ((Waterlogged) data).isWaterlogged()) {
return BlockFlags.F_WATER | BlockFlags.F_LIQUID | BlockFlags.F_WATERLOGGED;
}
return 0L;
}
@Override @Override
public double[] fetchBounds(int x, int y, int z) { public double[] fetchBounds(int x, int y, int z) {
// TODO: Fetch what's possible to fetch/guess (...). // TODO: Fetch what's possible to fetch/guess (...).
@@ -69,11 +107,37 @@ public class BlockCacheBukkitModern extends BlockCacheBukkit {
return shapeModel.getShape(this, world, x, y, z); return shapeModel.getShape(this, world, x, y, z);
} }
@Override
public double[] fetchVisualBounds(int x, int y, int z) {
Material mat = getType(x, y, z);
final BukkitShapeModel shapeModel = shapeModels.get(mat);
if (shapeModel == null) {
return super.fetchVisualBounds(x, y, z);
}
return shapeModel.getVisualShape(this, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
Material mat = getType(x, y, z);
final BukkitShapeModel shapeModel = shapeModels.get(mat);
if (shapeModel == null) {
return super.isCollisionSameVisual(x, y, z);
}
return shapeModel.isCollisionSameVisual(this, world, x, y, z);
}
@Override @Override
public boolean standsOnEntity(final Entity entity, final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ) { public boolean standsOnEntity(final Entity entity, final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ) {
if (!SchedulerHelper.isOwnedByCurrentRegion(entity)) {
return false;
}
try { try {
// TODO: Probably check vehicle ids too before doing this ? // TODO: Probably check vehicle ids too before doing this ?
for (final Entity vehicle : entity.getNearbyEntities(0.1, 2.0, 0.1)) { for (final Entity vehicle : entity.getNearbyEntities(0.1, 2.0, 0.1)) {
if (!SchedulerHelper.isOwnedByCurrentRegion(vehicle)) {
continue;
}
final EntityType type = vehicle.getType(); final EntityType type = vehicle.getType();
if (!MaterialUtil.isBoat(type) && type != EntityType.SHULKER) { // && !(vehicle instanceof Minecart)) if (!MaterialUtil.isBoat(type) && type != EntityType.SHULKER) { // && !(vehicle instanceof Minecart))
continue; continue;
@@ -27,6 +27,7 @@ import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
import fr.neatmonster.nocheatplus.compat.AttribUtil; import fr.neatmonster.nocheatplus.compat.AttribUtil;
import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.compat.BridgeMisc; import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.registry.BukkitAPIAccessFactory;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion; import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion; import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.components.modifier.IAttributeAccess; import fr.neatmonster.nocheatplus.components.modifier.IAttributeAccess;
@@ -66,6 +67,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
* @param id * @param id
* @return * @return
*/ */
@SuppressWarnings({"deprecation", "removal"})
private AttributeModifier getModifier(final AttributeInstance attrInst, final UUID id) { private AttributeModifier getModifier(final AttributeInstance attrInst, final UUID id) {
for (final AttributeModifier mod : attrInst.getModifiers()) { for (final AttributeModifier mod : attrInst.getModifiers()) {
if (id.equals(mod.getUniqueId())) { if (id.equals(mod.getUniqueId())) {
@@ -87,7 +89,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
@Override @Override
public double getSpeedMultiplier(final Player player) { public double getSpeedMultiplier(final Player player) {
final AttributeInstance attrInst = BridgeBukkitAPI.getSpeedAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getSpeedAttributeInstance(player);
final double val = attrInst.getValue() / attrInst.getBaseValue(); final double val = attrInst.getValue() / attrInst.getBaseValue();
final AttributeModifier mod = getModifier(attrInst, AttribUtil.ID_SPRINT_BOOST); final AttributeModifier mod = getModifier(attrInst, AttribUtil.ID_SPRINT_BOOST);
return mod == null ? val : (val / getMultiplier(mod)); return mod == null ? val : (val / getMultiplier(mod));
@@ -95,7 +97,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
@Override @Override
public double getSprintMultiplier(final Player player) { public double getSprintMultiplier(final Player player) {
final AttributeInstance attrInst = BridgeBukkitAPI.getSpeedAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getSpeedAttributeInstance(player);
final AttributeModifier mod = getModifier(attrInst, AttribUtil.ID_SPRINT_BOOST); final AttributeModifier mod = getModifier(attrInst, AttribUtil.ID_SPRINT_BOOST);
return mod == null ? 1.0 : getMultiplier(mod); return mod == null ? 1.0 : getMultiplier(mod);
} }
@@ -112,7 +114,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
gravity = thisMove.yDistance <= 0.0 && !Double.isInfinite(Bridge1_13.getSlowfallingAmplifier(player)) ? Magic.SLOW_FALL_GRAVITY : Magic.DEFAULT_GRAVITY; gravity = thisMove.yDistance <= 0.0 && !Double.isInfinite(Bridge1_13.getSlowfallingAmplifier(player)) ? Magic.SLOW_FALL_GRAVITY : Magic.DEFAULT_GRAVITY;
} }
else { else {
final AttributeInstance attrInst = BridgeBukkitAPI.getGravityAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getGravityAttributeInstance(player);
// Fail-safe. // Fail-safe.
if (attrInst == null) { if (attrInst == null) {
gravity = thisMove.yDistance <= 0.0 && !Double.isInfinite(Bridge1_13.getSlowfallingAmplifier(player)) ? Magic.SLOW_FALL_GRAVITY : Magic.DEFAULT_GRAVITY; gravity = thisMove.yDistance <= 0.0 && !Double.isInfinite(Bridge1_13.getSlowfallingAmplifier(player)) ? Magic.SLOW_FALL_GRAVITY : Magic.DEFAULT_GRAVITY;
@@ -131,7 +133,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return Magic.FALL_DAMAGE_DIST; return Magic.FALL_DAMAGE_DIST;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getSafeFallAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getSafeFallAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return Magic.FALL_DAMAGE_DIST; if (attrInst == null) return Magic.FALL_DAMAGE_DIST;
return MathUtil.clamp(attrInst.getValue(), -1024.0, 1024.0); return MathUtil.clamp(attrInst.getValue(), -1024.0, 1024.0);
@@ -143,7 +145,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return 1.0; return 1.0;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getFallMultAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getFallMultAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return 1.0; if (attrInst == null) return 1.0;
return MathUtil.clamp(attrInst.getValue(), 1.0, 100.0); return MathUtil.clamp(attrInst.getValue(), 1.0, 100.0);
@@ -155,7 +157,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return 1.0; return 1.0;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getBreakSpeedAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getBreakSpeedAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return 1.0; if (attrInst == null) return 1.0;
return MathUtil.clamp(attrInst.getValue(), 1.0, 1024.0); return MathUtil.clamp(attrInst.getValue(), 1.0, 1024.0);
@@ -167,7 +169,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return 1.0; return 1.0;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getJumpPowerAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getJumpPowerAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return 1.0; if (attrInst == null) return 1.0;
final double val = attrInst.getValue() / attrInst.getBaseValue(); final double val = attrInst.getValue() / attrInst.getBaseValue();
@@ -186,7 +188,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return 4.5; return 4.5;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getBlockInteractionRangeAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getBlockInteractionRangeAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return 4.5; if (attrInst == null) return 4.5;
return MathUtil.clamp(attrInst.getValue(), 0.0, 64.0); return MathUtil.clamp(attrInst.getValue(), 0.0, 64.0);
@@ -198,7 +200,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
// Doesn't exist. Calculate manually. // Doesn't exist. Calculate manually.
return 3.0; return 3.0;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getEntityInteractionRangeAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getEntityInteractionRangeAttributeInstance(player);
// Fail-safe // Fail-safe
if (attrInst == null) return 3.0; if (attrInst == null) return 3.0;
return MathUtil.clamp(attrInst.getValue(), 0.0, 64.0); return MathUtil.clamp(attrInst.getValue(), 0.0, 64.0);
@@ -219,7 +221,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
final MovingConfig cc = DataManager.getPlayerData(player).getGenericInstance(MovingConfig.class); final MovingConfig cc = DataManager.getPlayerData(player).getGenericInstance(MovingConfig.class);
return cc.sfStepHeight; return cc.sfStepHeight;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getStepHeightAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getStepHeightAttributeInstance(player);
if (attrInst == null) { if (attrInst == null) {
// Fail-safe. // Fail-safe.
if (player.isInsideVehicle()) { if (player.isInsideVehicle()) {
@@ -277,7 +279,7 @@ public class BukkitAttributeAccess implements IAttributeAccess {
if (ServerVersion.isLowerThan("1.20.5")) { if (ServerVersion.isLowerThan("1.20.5")) {
return 1.0; return 1.0;
} }
final AttributeInstance attrInst = BridgeBukkitAPI.getScaleAttributeInstance(player); final AttributeInstance attrInst = BukkitAPIAccessFactory.getBukkitAccess().getScaleAttributeInstance(player);
// Fail-safe. // Fail-safe.
if (attrInst == null) return 1.0; if (attrInst == null) return 1.0;
return MathUtil.clamp(attrInst.getValue(), 0.0625, 16.0); return MathUtil.clamp(attrInst.getValue(), 0.0625, 16.0);
@@ -41,7 +41,7 @@ public class MCAccessBukkit extends MCAccessBukkitBase implements BlockPropertie
} }
else if (guessItchyBlock(mat)) { else if (guessItchyBlock(mat)) {
// Uncertain bounding-box, allow passing through. // Uncertain bounding-box, allow passing through.
long flags = BlockFlags.F_IGN_PASSABLE; long flags = BlockFlags.F_IGN_PASSABLE_CHECK;
if ((BlockFlags.hasAnyFlag(flags, BlockFlags.SOLID_GROUND))) { if ((BlockFlags.hasAnyFlag(flags, BlockFlags.SOLID_GROUND))) {
// Block can be ground, so allow standing on any height. // Block can be ground, so allow standing on any height.
flags |= BlockFlags.F_GROUND | BlockFlags.F_GROUND_HEIGHT; flags |= BlockFlags.F_GROUND | BlockFlags.F_GROUND_HEIGHT;
@@ -80,7 +80,7 @@ public class MCAccessBukkitBase implements MCAccess {
* Skip fully passable blocks (partially passable blocks may be itchy, * Skip fully passable blocks (partially passable blocks may be itchy,
* though slabs will be easy to handle). * though slabs will be easy to handle).
*/ */
if (BlockFlags.hasAnyFlag(flags, BlockFlags.F_IGN_PASSABLE)) { if (BlockFlags.hasAnyFlag(flags, BlockFlags.F_IGN_PASSABLE_CHECK)) {
// TODO: Blocks with min_height may actually be ok, if xz100 and some height are set. // TODO: Blocks with min_height may actually be ok, if xz100 and some height are set.
if (BlockFlags.hasNoFlags(flags, if (BlockFlags.hasNoFlags(flags,
BlockFlags.F_GROUND_HEIGHT BlockFlags.F_GROUND_HEIGHT
@@ -22,43 +22,7 @@ import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.compat.AlmostBoolean; import fr.neatmonster.nocheatplus.compat.AlmostBoolean;
import fr.neatmonster.nocheatplus.compat.blocks.init.BlockInit; import fr.neatmonster.nocheatplus.compat.blocks.init.BlockInit;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitAnvil; import fr.neatmonster.nocheatplus.compat.bukkit.model.*;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitBamboo;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitBell;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitCake;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitCauldron;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitChorusPlant;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitCocoa;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitDirectionalCentered;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitDoor;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitDripLeaf;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitDripStone;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitEndPortalFrame;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitFence;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitFetchableBound;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitFetchableBounds;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitGate;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitGrindStone;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitHopper;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitLadder;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitLantern;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitLevelled;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitPiston;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitPistonHead;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitPowderSnow;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitRail;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitSeaPickle;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitShapeModel;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitShulkerBox;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitSlab;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitSnow;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitStairs;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitStatic;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitTrapDoor;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitTurtleEgg;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitWall;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitWallHead;
import fr.neatmonster.nocheatplus.compat.bukkit.model.BukkitWaterPlant;
import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectBase; import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectBase;
import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectDamageSource; import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectDamageSource;
import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectDamageSources; import fr.neatmonster.nocheatplus.compat.cbreflect.reflect.ReflectDamageSources;
@@ -101,8 +65,8 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
0.25, 0.125, 0.25, 0.75, 0.875, 0.75 0.25, 0.125, 0.25, 0.75, 0.875, 0.75
); );
private static final BukkitShapeModel MODEL_HOPPER = new BukkitHopper(); private static final BukkitShapeModel MODEL_HOPPER = new BukkitHopper();
private static final BukkitShapeModel MODEL_CAULDRON = new BukkitCauldron(0.1875, 0.125, 0.8125, 0.0625); private static final BukkitShapeModel MODEL_CAULDRON = new BukkitCauldron();
private static final BukkitShapeModel MODEL_COMPOSTER = new BukkitCauldron(0.0, 0.125, 1.0, 0.125); private static final BukkitShapeModel MODEL_COMPOSTER = new BukkitComposter(0.0, 0.125, 1.0, 0.125);
private static final BukkitShapeModel MODEL_PISTON_HEAD = new BukkitPistonHead(); private static final BukkitShapeModel MODEL_PISTON_HEAD = new BukkitPistonHead();
private static final BukkitShapeModel MODEL_BELL = new BukkitBell(); private static final BukkitShapeModel MODEL_BELL = new BukkitBell();
private static final BukkitShapeModel MODEL_ANVIL = new BukkitAnvil(); private static final BukkitShapeModel MODEL_ANVIL = new BukkitAnvil();
@@ -135,8 +99,8 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
private static final BukkitShapeModel MODEL_END_ROD = new BukkitDirectionalCentered(0.375, 1.0, false); private static final BukkitShapeModel MODEL_END_ROD = new BukkitDirectionalCentered(0.375, 1.0, false);
// Blocks that have a different shape with neighbor blocks (bukkit takes care though). // Blocks that have a different shape with neighbor blocks (bukkit takes care though).
private static final BukkitShapeModel MODEL_THIN_FENCE = new BukkitFence(0.4375, 1.0); private static final BukkitShapeModel MODEL_THIN_FENCE = new BukkitFence(0.4375, 1.0, true);
private static final BukkitShapeModel MODEL_THICK_FENCE = new BukkitFence(0.375, 1.5); private static final BukkitShapeModel MODEL_THICK_FENCE = new BukkitFence(0.375, 1.5, false);
private static final BukkitShapeModel MODEL_THICK_FENCE2 = new BukkitWall(0.25, 1.5, 0.3125); // .75 .25 0 max: .25 .75 .5 private static final BukkitShapeModel MODEL_THICK_FENCE2 = new BukkitWall(0.25, 1.5, 0.3125); // .75 .25 0 max: .25 .75 .5
private static final BukkitShapeModel MODEL_WALL_HEAD = new BukkitWallHead(); private static final BukkitShapeModel MODEL_WALL_HEAD = new BukkitWallHead();
@@ -144,15 +108,17 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
private static final BukkitShapeModel MODEL_CAMPFIRE = new BukkitStatic(0.0, 0.4375); private static final BukkitShapeModel MODEL_CAMPFIRE = new BukkitStatic(0.0, 0.4375);
private static final BukkitShapeModel MODEL_BAMBOO = new BukkitBamboo(); private static final BukkitShapeModel MODEL_BAMBOO = new BukkitBamboo();
private static final BukkitShapeModel MODEL_POINTED_DRIPSTONE = new BukkitDripStone(); private static final BukkitShapeModel MODEL_POINTED_DRIPSTONE = new BukkitDripStone();
private static final BukkitShapeModel MODEL_SCAFFOLDING = new BukkitScaffolding();
private static final BukkitShapeModel MODEL_WATER_PLANTS = new BukkitWaterPlant(); private static final BukkitShapeModel MODEL_WATER_PLANTS = new BukkitWaterPlant();
private static final BukkitShapeModel MODEL_LILY_PAD = new BukkitStatic(0.0625, 0.09375); private static final BukkitShapeModel MODEL_LILY_PAD = new BukkitLilyPad();
private static final BukkitShapeModel MODEL_FLOWER_POT = new BukkitStatic(0.3125, 0.375); private static final BukkitShapeModel MODEL_FLOWER_POT = new BukkitStatic(0.3125, 0.375);
private static final BukkitShapeModel MODEL_LANTERN = new BukkitLantern(); private static final BukkitShapeModel MODEL_LANTERN = new BukkitLantern();
private static final BukkitShapeModel MODEL_CONDUIT = new BukkitStatic(0.3125, 0.3125, 0.3125, 0.6875, 0.6875, 0.6875); private static final BukkitShapeModel MODEL_CONDUIT = new BukkitStatic(0.3125, 0.3125, 0.3125, 0.6875, 0.6875, 0.6875);
private static final BukkitShapeModel MODEL_GROUND_HEAD = new BukkitStatic(0.25, 0.5); private static final BukkitShapeModel MODEL_GROUND_HEAD = new BukkitStatic(0.25, 0.5);
private static final BukkitShapeModel MODEL_SINGLE_CHEST = new BukkitStatic(0.0625, 0.875); private static final BukkitShapeModel MODEL_CHEST = new BukkitChest();
private static final BukkitShapeModel MODEL_HONEY_BLOCK = new BukkitStatic(0.0625, 0.9375); private static final BukkitShapeModel MODEL_HONEY_BLOCK = new BukkitStatic(0.0625, 0.9375);
private static final BukkitShapeModel MODEL_SCULK_SHRIEKER = new BukkitStatic(0.0, 0.5); private static final BukkitShapeModel MODEL_SCULK_SHRIEKER = new BukkitStatic(0.0, 0.5);
private static final BukkitShapeModel MODEL_DRIED_GHAST = new BukkitStatic(0.1875f, 0.0f, 0.1875f, 0.8125f, 0.625f, 0.8125f); // NMS bounds: x1,y1,z1,x2,y2,y2 (y2 = 10/16 = 0.625)
// Static blocks with full height sorted by inset. // Static blocks with full height sorted by inset.
private static final BukkitShapeModel MODEL_INSET16_1_HEIGHT100 = new BukkitStatic(0.0625, 1.0); private static final BukkitShapeModel MODEL_INSET16_1_HEIGHT100 = new BukkitStatic(0.0625, 1.0);
@@ -163,7 +129,7 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
private static final BukkitShapeModel MODEL_XZ100_HEIGHT8_3 = new BukkitStatic(0.375); private static final BukkitShapeModel MODEL_XZ100_HEIGHT8_3 = new BukkitStatic(0.375);
private static final BukkitShapeModel MODEL_XZ100_HEIGHT16_9 = new BukkitStatic(0.5625); private static final BukkitShapeModel MODEL_XZ100_HEIGHT16_9 = new BukkitStatic(0.5625);
private static final BukkitShapeModel MODEL_XZ100_HEIGHT4_3 = new BukkitStatic(0.75); private static final BukkitShapeModel MODEL_XZ100_HEIGHT4_3 = new BukkitStatic(0.75);
private static final BukkitShapeModel MODEL_XZ100_HEIGHT16_15 = new BukkitStatic(0.9375); private static final BukkitShapeModel MODEL_XZ100_HEIGHT16_15 = new BukkitDirtLike();
private static final BukkitShapeModel MODEL_XZ100_HEIGHT8_7 = new BukkitStatic(0.875); private static final BukkitShapeModel MODEL_XZ100_HEIGHT8_7 = new BukkitStatic(0.875);
public MCAccessBukkitModern() { public MCAccessBukkitModern() {
@@ -183,7 +149,7 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
@Override @Override
public String getMCVersion() { public String getMCVersion() {
return "1.13-1.21"; return "1.13-26";
} }
@Override @Override
@@ -233,12 +199,19 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
processedBlocks.add(mat); processedBlocks.add(mat);
} }
for (final Material mat : BridgeMaterial.getAllBlocks( for (final Material mat : BridgeMaterial.getAllBlocks(
"light", "glow_lichen", "big_dripleaf_stem", "light", "glow_lichen", "big_dripleaf_stem")) {
// TODO: Not fully tested
"scaffolding")) {
processedBlocks.add(mat); processedBlocks.add(mat);
} }
// Shelf mushrooms
for (final Material mat : BridgeMaterial.getAllBlocks("shelf_mushroom")) {
addModel(mat, MODEL_AUTO_FETCH);
}
for (Material mat : MaterialUtil.STRAW_BEDS) {
addModel(mat, MODEL_AUTO_FETCH);
}
// Candle // Candle
for (Material mat : MaterialUtil.ALL_CANDLES) { for (Material mat : MaterialUtil.ALL_CANDLES) {
addModel(mat, MODEL_AUTO_FETCH); addModel(mat, MODEL_AUTO_FETCH);
@@ -249,12 +222,22 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
addModel(mat, MODEL_AUTO_FETCH); addModel(mat, MODEL_AUTO_FETCH);
} }
// Shelves
for (Material mat : MaterialUtil.WOODEN_SHELVES) {
addModel(mat, MODEL_AUTO_FETCH);
}
// new flower, and others // new flower, and others
for (Material mat : BridgeMaterial.getAllBlocks( for (Material mat : BridgeMaterial.getAllBlocks(
"azalea", "flowering_azalea", "azalea", "flowering_azalea",
"sculk_sensor", "frogspawn", "sculk_sensor", "frogspawn",
"sniffer_egg", "decorated_pot", "pitcher_crop", "calibrated_sculk_sensor", "bubble_column", "sniffer_egg", "decorated_pot", "pitcher_crop", "calibrated_sculk_sensor",
"closed_eyeblossom", "open_eyeblossom", "pale_hanging_moss", "resin_clump")) { "closed_eyeblossom", "open_eyeblossom", "pale_hanging_moss", "resin_clump","cactus_flower",
"short_dry_grass", "tall_dry_grass", "golden_dandelion")) {
addModel(mat, MODEL_AUTO_FETCH);
}
for (final Material mat : BridgeMaterial.getAllBlocks("pale_moss_carpet")) {
addModel(mat, MODEL_AUTO_FETCH); addModel(mat, MODEL_AUTO_FETCH);
} }
@@ -264,11 +247,27 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
addModel(mat, MODEL_AUTO_FETCH); addModel(mat, MODEL_AUTO_FETCH);
} }
// Stonecutter and legacy chains
for (Material mat : BridgeMaterial.getAllBlocks( for (Material mat : BridgeMaterial.getAllBlocks(
"stonecutter", "chain")) { "stonecutter", "chain")) {
addModel(mat, MODEL_AUTO_FETCH_LEGACY); addModel(mat, MODEL_AUTO_FETCH_LEGACY);
} }
// Modern iron chains
if (BridgeMaterial.IRON_CHAIN != null) {
addModel(BridgeMaterial.IRON_CHAIN, MODEL_AUTO_FETCH);
}
// Copper chains
for (Material mat : MaterialUtil.COPPER_CHAINS) {
addModel(mat, MODEL_AUTO_FETCH_LEGACY);
}
// Copper golem statues.
for (Material mat : MaterialUtil.COPPER_GOLEM_STATUES) {
addModel(mat, MODEL_AUTO_FETCH);
}
// Camp fire // Camp fire
for (Material mat : BridgeMaterial.getAllBlocks( for (Material mat : BridgeMaterial.getAllBlocks(
"campfire", "soul_campfire")) { "campfire", "soul_campfire")) {
@@ -299,6 +298,7 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
addModel(BridgeMaterial.CAKE, MODEL_CAKE); addModel(BridgeMaterial.CAKE, MODEL_CAKE);
// End Rod / Lightning Rod. // End Rod / Lightning Rod.
// Do they actually have the same model? Doesn't the lightning rod have a slightly different box/bulb at the top? <- No, purely visual.
for (Material mat : MaterialUtil.RODS) { for (Material mat : MaterialUtil.RODS) {
addModel(mat, MODEL_END_ROD); addModel(mat, MODEL_END_ROD);
} }
@@ -353,12 +353,15 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
addModel(mat, MODEL_XZ100_HEIGHT16_15); addModel(mat, MODEL_XZ100_HEIGHT16_15);
} }
// Thin fence: Glass panes, iron bars. // Thin fence: Glass panes, iron bars, copper bars.
for (final Material mat : MaterialUtil.addBlocks( for (final Material mat : MaterialUtil.addBlocks(
MaterialUtil.GLASS_PANES, MaterialUtil.GLASS_PANES,
BridgeMaterial.IRON_BARS)) { BridgeMaterial.IRON_BARS)) {
addModel(mat, MODEL_THIN_FENCE); addModel(mat, MODEL_THIN_FENCE);
} }
for (final Material mat : MaterialUtil.COPPER_BARS) {
addModel(mat, MODEL_THIN_FENCE);
}
// Slabs // Slabs
for (final Material mat : MaterialUtil.SLABS) { for (final Material mat : MaterialUtil.SLABS) {
@@ -371,10 +374,12 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
} }
// Chests. // Chests.
// TOOD: Might add a facing/directional extension for double chests.
for (Material mat : BridgeMaterial.getAllBlocks( for (Material mat : BridgeMaterial.getAllBlocks(
"chest", "trapped_chest", "ender_chest")) { "chest", "trapped_chest", "ender_chest")) {
addModel(mat, MODEL_SINGLE_CHEST); addModel(mat, MODEL_CHEST);
}
for (Material mat : MaterialUtil.COPPER_CHESTS) {
addModel(mat, MODEL_CHEST);
} }
// Beds // Beds
@@ -447,6 +452,9 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
"lantern", "soul_lantern")) { "lantern", "soul_lantern")) {
addModel(mat, MODEL_LANTERN); addModel(mat, MODEL_LANTERN);
} }
for (Material mat : MaterialUtil.COPPER_LANTERNS) {
addModel(mat, MODEL_LANTERN);
}
// Piston. // Piston.
for (Material mat : BridgeMaterial.getAllBlocks( for (Material mat : BridgeMaterial.getAllBlocks(
@@ -496,10 +504,18 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
mt = BridgeMaterial.getBlock("bamboo"); mt = BridgeMaterial.getBlock("bamboo");
if (mt != null) addModel(mt, MODEL_BAMBOO); if (mt != null) addModel(mt, MODEL_BAMBOO);
// Scaffolding
mt = BridgeMaterial.getBlock("scaffolding");
if (mt != null) addModel(mt, MODEL_SCAFFOLDING);
// Dripstone. // Dripstone.
mt = BridgeMaterial.getBlock("pointed_dripstone"); mt = BridgeMaterial.getBlock("pointed_dripstone");
if (mt != null) addModel(mt, MODEL_POINTED_DRIPSTONE); if (mt != null) addModel(mt, MODEL_POINTED_DRIPSTONE);
// Sulfur spike
mt = BridgeMaterial.getBlock("sulfur_spike");
if (mt != null) addModel(mt, MODEL_POINTED_DRIPSTONE);
// Bell. // Bell.
mt = BridgeMaterial.getBlock("bell"); mt = BridgeMaterial.getBlock("bell");
if (mt != null) addModel(mt, MODEL_BELL); if (mt != null) addModel(mt, MODEL_BELL);
@@ -532,6 +548,10 @@ public class MCAccessBukkitModern extends MCAccessBukkit {
mt = BridgeMaterial.getBlock("heavy_core"); mt = BridgeMaterial.getBlock("heavy_core");
if (mt != null) addModel(mt, MODEL_GROUND_HEAD); if (mt != null) addModel(mt, MODEL_GROUND_HEAD);
// Dried ghast
mt = BridgeMaterial.getBlock("dried_ghast");
if (mt != null) addModel(mt, MODEL_DRIED_GHAST);
// Sort to processed by flags. // Sort to processed by flags.
for (final Material mat : Material.values()) { for (final Material mat : Material.values()) {
final long flags = BlockFlags.getBlockFlags(mat); final long flags = BlockFlags.getBlockFlags(mat);
@@ -76,4 +76,14 @@ public abstract class AbstractBukkitCentered implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -20,6 +20,8 @@ import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional; import org.bukkit.block.data.Directional;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitAnvil implements BukkitShapeModel { public class BukkitAnvil implements BukkitShapeModel {
@@ -28,11 +30,14 @@ public class BukkitAnvil implements BukkitShapeModel {
public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final BlockData blockData = block.getBlockData(); final BlockData blockData = block.getBlockData();
final IPlayerData data = blockCache.getPlayerData();
final boolean legacy = data != null && data.getClientVersion().isLowerThan(ClientVersion.V_1_13);
if (blockData instanceof Directional) { if (blockData instanceof Directional) {
BlockFace face = ((Directional) blockData).getFacing(); BlockFace face = ((Directional) blockData).getFacing();
switch (face) { switch (face) {
case NORTH: case NORTH:
case SOUTH: case SOUTH:
if (legacy) return new double[] {0.125, 0.0, 0.0, 0.875, 1.0, 1.0};
return new double[] { return new double[] {
// Bottom // Bottom
0.125, 0.0, 0.125, 0.875, 0.25, 0.875, 0.125, 0.0, 0.125, 0.875, 0.25, 0.875,
@@ -42,6 +47,7 @@ public class BukkitAnvil implements BukkitShapeModel {
}; };
case WEST: case WEST:
case EAST: case EAST:
if (legacy) return new double[] {0.0, 0.0, 0.125, 1.0, 1.0, 0.875};
return new double[] { return new double[] {
// Bottom // Bottom
0.125, 0.0, 0.125, 0.875, 0.25, 0.875, 0.125, 0.0, 0.125, 0.875, 0.25, 0.875,
@@ -60,4 +66,14 @@ public class BukkitAnvil implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -63,4 +63,14 @@ public class BukkitBamboo implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -123,4 +123,14 @@ public class BukkitBell implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -45,4 +45,14 @@ public class BukkitCake implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -16,13 +16,32 @@ package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World; import org.bukkit.World;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitCauldron implements BukkitShapeModel { public class BukkitCauldron implements BukkitShapeModel {
private final double[] bounds; private final static double[] new1_13_2Bounds = {
0.0, 0.0, 0.0, 0.125, 1.0, 0.25,
0.0, 0.0, 0.75, 0.125, 1.0, 1.0,
0.125, 0.0, 0.0, 0.25, 1.0, 0.125,
0.125, 0.0, 0.875, 0.25, 1.0, 1.0,
0.75, 0.0, 0.0, 1.0, 1.0, 0.125,
0.75, 0.0, 0.875, 1.0, 1.0, 1.0,
0.875, 0.0, 0.125, 1.0, 1.0, 0.25,
0.875, 0.0, 0.75, 1.0, 1.0, 0.875,
0.0, 0.1875, 0.25, 1.0, 0.25, 0.75,
0.125, 0.1875, 0.125, 0.875, 0.25, 0.25,
0.125, 0.1875, 0.75, 0.875, 0.25, 0.875,
0.25, 0.1875, 0.0, 0.75, 1.0, 0.125,
0.25, 0.1875, 0.875, 0.75, 1.0, 1.0,
0.0, 0.25, 0.25, 0.125, 1.0, 0.75,
0.875, 0.25, 0.25, 1.0, 1.0, 0.75};
private final static double[] new1_13Bounds = makeCauldron(0.1875, 0.125, 0.8125, 0.0625);
private final static double[] legacyBounds = makeCauldron(0.0, 0.125, 1.0, 0.3125);
public BukkitCauldron(double minY, double sideWidth, double sideHeight, double coreHeight) { private static double[] makeCauldron(double minY, double sideWidth, double sideHeight, double coreHeight) {
bounds = new double[] { return new double[] {
// Core // Core
sideWidth, minY, sideWidth, 1 - sideWidth, minY + coreHeight, 1 - sideWidth, sideWidth, minY, sideWidth, 1 - sideWidth, minY + coreHeight, 1 - sideWidth,
// 4 side // 4 side
@@ -35,11 +54,31 @@ public class BukkitCauldron implements BukkitShapeModel {
@Override @Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) { public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
return bounds; IPlayerData pData = blockCache.getPlayerData();
if (pData != null) {
if (pData.getClientVersion().isLowerThan(ClientVersion.V_1_13)) {
return legacyBounds;
} else if (pData.getClientVersion().isLowerThan(ClientVersion.V_1_13_2)) {
return new1_13Bounds;
} else return new1_13_2Bounds;
}
return new1_13Bounds;
} }
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return new double[] {0.0, 0.1875, 0.0, 1.0, 1.0, 1.0};
//return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
// Should be true , but current implementation of Visible check of NCP can't do complex ray-trace for performance wise and technical limitations
return false;
}
} }
@@ -0,0 +1,99 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Chest;
import org.bukkit.block.data.type.Chest.Type;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitChest implements BukkitShapeModel {
//TODO: With/without this, NCP still throw false when nearby doubled chest. Something else was wrong. Do note flags are correct too: (CHEST / SOLID+GROUND) BlockProps(2.5 / ToolProps(AXE/WOOD) / [3750, 1900, 950, 750, 600, 450, 400])
private final double[] NORTH_AABB = new double[] {0.0625, 0.0, 0.0, 0.9375, 0.875, 0.9375};
private final double[] SOUTH_AABB = new double[] {0.0625, 0.0, 0.0625, 0.9375, 0.875, 1.0};
private final double[] WEST_AABB = new double[] {0.0, 0.0, 0.0625, 0.9375, 0.875, 0.9375};
private final double[] EAST_AABB = new double[] {0.0625, 0.0, 0.0625, 1.0, 0.875, 0.9375};
private final double[] SINGLE = new double[] {0.0625, 0.0, 0.0625, 0.9375, 0.875, 0.9375};
public static BlockFace getConnectedDirection(BlockFace facing, Type chestType) {
return chestType == Type.LEFT ? getClockWise(facing) : getCounterClockWise(facing);
}
public static BlockFace getCounterClockWise(BlockFace dir) {
switch (dir) {
case NORTH: return BlockFace.WEST;
case SOUTH: return BlockFace.EAST;
case WEST: return BlockFace.SOUTH;
case EAST: return BlockFace.NORTH;
default: throw new IllegalStateException("Unable to get CCW facing of " + dir);
}
}
public static BlockFace getClockWise(BlockFace dir) {
switch (dir) {
case NORTH: return BlockFace.EAST;
case SOUTH: return BlockFace.WEST;
case WEST: return BlockFace.NORTH;
case EAST: return BlockFace.SOUTH;
default: throw new IllegalStateException("Unable to get Y-rotated facing of " + dir);
}
}
@Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z);
final BlockState state = block.getState();
final BlockData blockData = state.getBlockData();
if (blockData instanceof Chest) {
final Chest chest = (Chest) blockData;
final Type chestType = chest.getType();
if (chestType == Type.SINGLE) {
return SINGLE;
}
switch (getConnectedDirection(chest.getFacing(), chestType)) {
case NORTH:
default:
return NORTH_AABB;
case SOUTH:
return SOUTH_AABB;
case WEST:
return WEST_AABB;
case EAST:
return EAST_AABB;
}
}
return SINGLE;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0;
}
}
@@ -14,6 +14,10 @@
*/ */
package fr.neatmonster.nocheatplus.compat.bukkit.model; package fr.neatmonster.nocheatplus.compat.bukkit.model;
import java.util.HashSet;
import java.util.Set;
import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
@@ -22,52 +26,117 @@ import org.bukkit.block.data.MultipleFacing;
import org.bukkit.util.BoundingBox; import org.bukkit.util.BoundingBox;
import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
import fr.neatmonster.nocheatplus.utilities.collision.ShapeUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitChorusPlant implements BukkitShapeModel { public class BukkitChorusPlant implements BukkitShapeModel {
private static final BlockFace[] directions = new BlockFace[]{BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN};
private static final double[][] modernShapes = makeShapes();
@Override @Override
public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
if (Bridge1_13.hasBoundingBox()) { // Server is 1.13.2 higher
BoundingBox bd = block.getBoundingBox(); if (Bridge1_13.hasBuiltInRayTracing()) {
return new double[] {bd.getMinX()-x, 0.1875, bd.getMinZ()-z, bd.getMaxX()-x, bd.getMaxY()-y, bd.getMaxZ()-z}; double[] res = {};
for (BoundingBox box : block.getCollisionShape().getBoundingBoxes()) {
res = ShapeUtils.add(res, AxisAlignedBBUtils.toArray(box));
}
if (res.length == 0) return null;
return res;
} else {
// Server is 1.13
final BlockData blockData = block.getBlockData();
if (blockData instanceof MultipleFacing) {
final MultipleFacing chorusplant = (MultipleFacing) blockData;
return modernShapes[getAABBIndex(chorusplant.getFaces())];
}
} }
final BlockData blockData = block.getBlockData(); return new double[] {0,0,0,1.0,1.0,1.0};
double[] res = new double[] {0.187, 0.188, 0.187, 1.0 - 0.187, 0.8125, 1.0 - 0.187}; }
if (blockData instanceof MultipleFacing) {
final MultipleFacing chorusplant = (MultipleFacing) blockData; private static double[][] makeShapes() {
for (final BlockFace face : chorusplant.getFaces()) { float min = 0.5F - (float) 0.3125;
switch (face) { float max = 0.5F + (float) 0.3125;
//case DOWN: double[] base = {min, min, min, max, max, max};
// res[1]=0.0; double[][] cs = new double[directions.length][];
// break;
case UP: for (int i = 0; i < directions.length; i++) {
res[4]=1.0; BlockFace dir = directions[i];
break; cs[i] = new double[] {0.5D + Math.min(-(float) 0.3125, (double) dir.getModX() * 0.5D), 0.5D + Math.min(-(float) 0.3125, (double) dir.getModY() * 0.5D), 0.5D + Math.min(-(float) 0.3125, (double) dir.getModZ() * 0.5D),
case SOUTH: 0.5D + Math.max((float) 0.3125, (double) dir.getModX() * 0.5D), 0.5D + Math.max((float) 0.3125, (double) dir.getModY() * 0.5D), 0.5D + Math.max((float) 0.3125, (double) dir.getModZ() * 0.5D)
res[5]=1.0; };
break;
case NORTH:
res[2]=0.0;
break;
case WEST:
res[0]=0.0;
break;
case EAST:
res[3]=1.0;
break;
default:
break;
}
}
} }
return res;
double[][] cs2 = new double[64][];
for (int k = 0; k < 64; k++) {
double[] tmp = base;
for (int j = 0; j < directions.length; j++) {
if ((k & 1 << j) != 0) {
tmp = ShapeUtils.merge(tmp, cs[j]);
}
}
cs2[k] = tmp;
}
return cs2;
}
private Set<BlockFace> getLegacyFaces(BlockCache blockCache, World world, int x, int y, int z) {
Set<BlockFace> faces = new HashSet<>();
Material upBlock = blockCache.getType(x, y+1, z);
Material downBlock = blockCache.getType(x, y-1, z);
Material northBlock = blockCache.getType(x, y, z-1);
Material southBlock = blockCache.getType(x, y, z+1);
Material westBlock = blockCache.getType(x-1, y, z);
Material eastBlock = blockCache.getType(x+1, y, z);
if (downBlock == Material.CHORUS_PLANT || downBlock == Material.END_STONE || downBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.DOWN);
}
if (upBlock == Material.CHORUS_PLANT || upBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.UP);
}
if (northBlock == Material.CHORUS_PLANT || northBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.NORTH);
}
if (southBlock == Material.CHORUS_PLANT || southBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.SOUTH);
}
if (westBlock == Material.CHORUS_PLANT || westBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.WEST);
}
if (eastBlock == Material.CHORUS_PLANT || eastBlock == Material.CHORUS_FLOWER) {
faces.add(BlockFace.EAST);
}
return faces;
}
private int getAABBIndex(Set<BlockFace> faces) {
int i = 0;
for (int j = 0; j < directions.length; ++j) {
if (faces.contains(directions[j])) {
i |= 1 << j;
}
}
return i;
} }
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -93,4 +93,14 @@ public class BukkitCocoa implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -0,0 +1,54 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitComposter implements BukkitShapeModel {
private final double[] bounds;
public BukkitComposter(double minY, double sideWidth, double sideHeight, double coreHeight) {
bounds = new double[] {
// Core
sideWidth, minY, sideWidth, 1 - sideWidth, minY + coreHeight, 1 - sideWidth,
// 4 side
0.0, minY, 0.0, 1.0, minY + sideHeight, sideWidth,
0.0, minY, 1.0 - sideWidth, 1.0, minY + sideHeight, 1.0,
0.0, minY, 0.0, sideWidth, minY + sideHeight, 1.0,
1.0 - sideWidth, minY, 0.0, 1.0, minY + sideHeight, 1.0
};
}
@Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
return bounds;
}
@Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0;
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
}
}
@@ -0,0 +1,51 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
/**
* Grass path and farm land shape model.
*/
public class BukkitDirtLike implements BukkitShapeModel {
@Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
// Farmland changed the height from 1.10 but too lazy and not many clients on this version. Taking shortcut!
final IPlayerData data = blockCache.getPlayerData();
if (data != null && data.getClientVersion().isLowerThan(ClientVersion.V_1_9)) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
}
return new double[] {0.0, 0.0, 0.0, 1.0, 0.9375, 1.0};
}
@Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0;
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
}
@@ -111,8 +111,18 @@ public class BukkitDoor implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -34,20 +34,28 @@ public class BukkitDripLeaf implements BukkitShapeModel {
case PARTIAL: case PARTIAL:
res[4] -= 0.125; res[4] -= 0.125;
return res; return res;
case UNSTABLE:
// WORKAROUND: Zero this bounding box if the tilt is unstable [NCP should take care of ground judgement then]
return new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
case NONE: case NONE:
case UNSTABLE:
return res; return res;
default: default:
break; return new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
} }
} }
return null; return null;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -14,15 +14,16 @@
*/ */
package fr.neatmonster.nocheatplus.compat.bukkit.model; package fr.neatmonster.nocheatplus.compat.bukkit.model;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
import fr.neatmonster.nocheatplus.utilities.math.MathUtil;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace; import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.PointedDripstone; import org.bukkit.block.data.type.PointedDripstone;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
import fr.neatmonster.nocheatplus.utilities.math.MathUtil;
public class BukkitDripStone implements BukkitShapeModel { public class BukkitDripStone implements BukkitShapeModel {
private static final double[] TIP_MERGE = {0.3125, 0.0, 0.3125, 0.6875, 1.0, 0.6875}; private static final double[] TIP_MERGE = {0.3125, 0.0, 0.3125, 0.6875, 1.0, 0.6875};
private static final double[] TIP_DOWN = {0.3125, 0.3125, 0.3125, 0.6875, 1.0, 0.6875}; private static final double[] TIP_DOWN = {0.3125, 0.3125, 0.3125, 0.6875, 1.0, 0.6875};
@@ -66,6 +67,11 @@ public class BukkitDripStone implements BukkitShapeModel {
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
private float calcOffset(long x, boolean shift, boolean bedrock) { private float calcOffset(long x, boolean shift, boolean bedrock) {
if (shift) x >>=8; if (shift) x >>=8;
x &= 0xFL; x &= 0xFL;
@@ -84,4 +90,9 @@ public class BukkitDripStone implements BukkitShapeModel {
private double[] offset(double[] input, double x, double y, double z) { private double[] offset(double[] input, double x, double y, double z) {
return new double[] {input[0] + x, input[1] + y, input[2] + z, input[3] + x, input[4] + y, input[5] + z}; return new double[] {input[0] + x, input[1] + y, input[2] + z, input[3] + x, input[4] + y, input[5] + z};
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -35,6 +35,11 @@ public class BukkitEndPortalFrame implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
private double[] getShape(boolean hasEye) { private double[] getShape(boolean hasEye) {
return hasEye ? new double[] {0.0, 0.0, 0.0, 1.0, 0.8125, 1.0, 0.25, 0.8125, 0.25, 0.75, 1.0, 0.75} return hasEye ? new double[] {0.0, 0.0, 0.0, 1.0, 0.8125, 1.0, 0.25, 0.8125, 0.25, 0.75, 1.0, 0.75}
: new double[] {0.0, 0.0, 0.0, 1.0, 0.8125, 1.0}; : new double[] {0.0, 0.0, 0.0, 1.0, 0.8125, 1.0};
@@ -44,4 +49,9 @@ public class BukkitEndPortalFrame implements BukkitShapeModel {
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -20,65 +20,142 @@ import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.MultipleFacing; import org.bukkit.block.data.MultipleFacing;
import org.bukkit.util.BoundingBox;
import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
import fr.neatmonster.nocheatplus.utilities.collision.ShapeUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitFence implements BukkitShapeModel { public class BukkitFence implements BukkitShapeModel {
private final double[] northplane;
private final double[] southplane;
private final double[] westplane;
private final double[] eastplane;
private final double[] northplaneB;
private final double[] southplaneB;
private final double[] westplaneB;
private final double[] eastplaneB;
private final double[] fullplane;
private final double[] eastwest;
private final double[] southnorth;
private final double[] baseState;
private final boolean cutEdgeOnLowVersions;
private final double minXZ; public BukkitFence(double inset, double height, boolean cutEdgeOnLowVersions) {
private final double maxXZ; this(inset, 1.0 - inset, height, cutEdgeOnLowVersions);
private final double height;
public BukkitFence(double inset, double height) {
this(inset, 1.0 - inset, height);
} }
public BukkitFence(double minXZ, double maxXZ, double height) { public BukkitFence(double minXZ, double maxXZ, double height, boolean cutEdgeOnLowVersions) {
this.minXZ = minXZ; this.cutEdgeOnLowVersions = cutEdgeOnLowVersions;
this.maxXZ = maxXZ; this.northplane = new double[] {minXZ, 0.0, 0.0, maxXZ, height, maxXZ};
this.height = height; this.southplane = new double[] {minXZ, 0.0, minXZ, maxXZ, height, 1.0};
this.westplane = new double[] {0.0, 0.0, minXZ, maxXZ, height, maxXZ};
this.eastplane = new double[] {minXZ, 0.0, minXZ, 1.0, height, maxXZ};
this.northplaneB = new double[] {minXZ, 0.0, 0.0, maxXZ, height, maxXZ - 0.0625};
this.southplaneB = new double[] {minXZ, 0.0, minXZ + 0.0625, maxXZ, height, 1.0};
this.westplaneB = new double[] {0.0, 0.0, minXZ, maxXZ - 0.0625, height, maxXZ};
this.eastplaneB = new double[] {minXZ + 0.0625, 0.0, minXZ, 1.0, height, maxXZ};
this.eastwest = ShapeUtils.merge(westplane, eastplane);
this.southnorth = ShapeUtils.merge(southplane, northplane);
this.fullplane = ShapeUtils.add(eastwest, southnorth);
this.baseState = new double[] {minXZ, 0.0, minXZ, maxXZ, height, maxXZ};
} }
@Override @Override
public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
// 13749998807909
// 86250001192093
// 0.1375, 0.8625
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final BlockState state = block.getState(); final IPlayerData data = blockCache.getPlayerData();
final BlockData blockData = state.getBlockData(); final boolean cutEdge = data != null && cutEdgeOnLowVersions && data.getClientVersion().isLowerThan(ClientVersion.V_1_9);
// Server is 1.13.2 higher and player not on 1.8
if (blockData instanceof MultipleFacing) { if (Bridge1_13.hasBuiltInRayTracing() && !cutEdge) {
// Note isPassableWorkaround for these (no voxel shapes / multi cuboid yet). double[] res = {};
final MultipleFacing fence = (MultipleFacing) blockData; for (BoundingBox box : block.getCollisionShape().getBoundingBoxes()) {
// TODO: If height > 1.0, check if it needs to be capped, provided relevant. res = ShapeUtils.add(res, AxisAlignedBBUtils.toArray(box));
double[] res = new double[] {minXZ, 0.0, minXZ, maxXZ, height, maxXZ}; }
for (final BlockFace face : fence.getFaces()) { if (res.length == 0) return null;
switch (face) { return res;
case EAST: } else {
res[3] = 1.0; boolean east = false;
break; boolean north = false;
case NORTH: boolean west = false;
res[2] = 0.0; boolean south = false;
break; final BlockState state = block.getState();
case WEST: final BlockData blockData = state.getBlockData();
res[0] = 0.0; if (blockData instanceof MultipleFacing) {
break; final MultipleFacing fence = (MultipleFacing) blockData;
case SOUTH: for (final BlockFace face : fence.getFaces()) {
res[5] = 1.0; switch (face) {
break; case EAST:
default: east = true;
break; break;
case NORTH:
north = true;
break;
case WEST:
west = true;
break;
case SOUTH:
south = true;
break;
default:
break;
}
} }
} }
return res; if (east && north && west && south) {
return fullplane;
}
double tmp[] = new double[0];
if (east && west) {
tmp = eastwest;
east = west = false;
} else if (south && north) {
tmp = southnorth;
south = north = false;
}
if (south) {
tmp = ShapeUtils.add(tmp, cutEdge && isNextDir(east, north, west, south) ? southplaneB : southplane);
}
if (north) {
tmp = ShapeUtils.add(tmp, cutEdge && isNextDir(east, north, west, south) ? northplaneB : northplane);
}
if (east) {
tmp = ShapeUtils.add(tmp, cutEdge && isNextDir(east, north, west, south) ? eastplaneB : eastplane);
}
if (west) {
tmp = ShapeUtils.add(tmp, cutEdge && isNextDir(east, north, west, south) ? westplaneB : westplane);
}
if (tmp.length == 0) {
tmp = ShapeUtils.add(tmp, cutEdge ? fullplane : baseState);
}
return tmp;
} }
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; }
private boolean isNextDir(boolean east, boolean north, boolean west, boolean south) {
return (north || south) && (west || east);
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
double a[] = getShape(blockCache, world, x, y, z);
for (int i = 1; i < (int)a.length / 6; i++) a[i*6-2] = 1.0;
return a;
} }
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
// TODO: Which one is better, return true and cap the height or return false and fetch twice?
// NOTE: Should be no different as current implementation doesn't do exact ray trace check
return true;
}
} }
@@ -20,6 +20,14 @@ import org.bukkit.util.BoundingBox;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
/**
* Fetches a single BoundingBox from Bukkit's Block#getBoundingBox().
* Returns a six-element double[] {minX, minY, minZ, maxX, maxY, maxZ}
* relative to the block position (world coords minus the block's x,y,z).
*
* This class returns one bounding box; BukkitFetchableBounds reads the VoxelShape and may return multiple boxes for complex
* collision shapes.
*/
public class BukkitFetchableBound implements BukkitShapeModel { public class BukkitFetchableBound implements BukkitShapeModel {
@Override @Override
@@ -29,8 +37,18 @@ public class BukkitFetchableBound implements BukkitShapeModel {
return new double[] {box.getMinX()-x, box.getMinY()-y, box.getMinZ()-z, box.getMaxX()-x, box.getMaxY()-y, box.getMaxZ()-z}; return new double[] {box.getMinX()-x, box.getMinY()-y, box.getMinZ()-z, box.getMaxX()-x, box.getMaxY()-y, box.getMaxZ()-z};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -20,31 +20,44 @@ import org.bukkit.util.BoundingBox;
import org.bukkit.util.VoxelShape; import org.bukkit.util.VoxelShape;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils; import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
import fr.neatmonster.nocheatplus.utilities.collision.ShapeUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
/**
* Fetches the block's collision VoxelShape and returns one or more bounding boxes
* as a concatenated double[] containing {minX,minY,minZ,maxX,maxY,maxZ} for each
* box. Coordinates are expressed relative to the block position.
*
* Difference from BukkitFetchableBound: this class may return multiple boxes
* (preserving complex/multi-part collision geometry); BukkitFetchableBound
* returns a single BoundingBox from Block#getBoundingBox().
*/
public class BukkitFetchableBounds implements BukkitShapeModel { public class BukkitFetchableBounds implements BukkitShapeModel {
@Override @Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) { public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final VoxelShape blockshape = block.getCollisionShape(); final VoxelShape blockShape = block.getCollisionShape();
double[] res = {}; double[] res = {};
for (BoundingBox box : blockshape.getBoundingBoxes()) { for (BoundingBox box : blockShape.getBoundingBoxes()) {
res = add(res, AxisAlignedBBUtils.toArray(box)); res = ShapeUtils.add(res, AxisAlignedBBUtils.toArray(box));
} }
if (res.length == 0) return null; if (res.length == 0) return null;
return res; return res;
} }
private double[] add(final double[] array1, final double[] array2) { @Override
final double[] newArray = new double[array1.length + array2.length]; public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
System.arraycopy(array1, 0, newArray, 0, array1.length); return getShape(blockCache, world, x, y, z);
System.arraycopy(array2, 0, newArray, array1.length, array2.length);
return newArray;
} }
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -61,6 +61,11 @@ public class BukkitGate implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
@@ -71,4 +76,9 @@ public class BukkitGate implements BukkitShapeModel {
} }
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -106,8 +106,18 @@ public class BukkitGrindStone implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -20,12 +20,24 @@ import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional; import org.bukkit.block.data.Directional;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitHopper implements BukkitShapeModel { public class BukkitHopper implements BukkitShapeModel {
@Override @Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) { public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
IPlayerData pData = blockCache.getPlayerData();
if (pData != null && pData.getClientVersion().isLowerThan(ClientVersion.V_1_13)) {
return new double[] {
0, 0, 0, 1, 0.625, 1,
0, 0.625, 0, 0.125, 1, 1,
0.875, 0.625, 0, 1, 1, 1,
0, 0.625, 0, 1, 1, 0.125,
0, 0.625, 0.875, 1, 1, 1
};
}
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final BlockData blockData = block.getBlockData(); final BlockData blockData = block.getBlockData();
if (blockData instanceof Directional) { if (blockData instanceof Directional) {
@@ -40,13 +52,11 @@ public class BukkitHopper implements BukkitShapeModel {
0.25, 0.25, 0.25, 0.75, 0.625, 0.75, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75,
// Bottom // Bottom
0.375, 0.25, 0.0, 0.625, 0.5, 0.25, 0.375, 0.25, 0.0, 0.625, 0.5, 0.25,
// Top
0.0, 0.6875, 0.0, 1.0, 1.0, 1.0
// 4 sides of hopper (top) // 4 sides of hopper (top)
//0.0, 0.6875, 0.0, 1.0, 1.0, 0.125, 0.0, 0.6875, 0.0, 1.0, 1.0, 0.125,
//0.0, 0.6875, 0.875, 1.0, 1.0, 1.0, 0.0, 0.6875, 0.875, 1.0, 1.0, 1.0,
//0.0, 0.6875, 0.0, 0.125, 1.0, 1.0, 0.0, 0.6875, 0.0, 0.125, 1.0, 1.0,
//0.875, 0.6875, 0.0, 1.0, 1.0, 1.0, 0.875, 0.6875, 0.0, 1.0, 1.0, 1.0,
}; };
case SOUTH: case SOUTH:
return new double[] { return new double[] {
@@ -56,13 +66,11 @@ public class BukkitHopper implements BukkitShapeModel {
0.25, 0.25, 0.25, 0.75, 0.625, 0.75, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75,
// Bottom // Bottom
0.375, 0.25, 0.75, 0.625, 0.5, 1.0, 0.375, 0.25, 0.75, 0.625, 0.5, 1.0,
// Top
0.0, 0.6875, 0.0, 1.0, 1.0, 1.0
// 4 sides of hopper (top) // 4 sides of hopper (top)
//0.0, 0.6875, 0.0, 1.0, 1.0, 0.125, 0.0, 0.6875, 0.0, 1.0, 1.0, 0.125,
//0.0, 0.6875, 0.875, 1.0, 1.0, 1.0, 0.0, 0.6875, 0.875, 1.0, 1.0, 1.0,
//0.0, 0.6875, 0.0, 0.125, 1.0, 1.0, 0.0, 0.6875, 0.0, 0.125, 1.0, 1.0,
//0.875, 0.6875, 0.0, 1.0, 1.0, 1.0, 0.875, 0.6875, 0.0, 1.0, 1.0, 1.0,
}; };
case WEST: case WEST:
return new double[] { return new double[] {
@@ -72,13 +80,11 @@ public class BukkitHopper implements BukkitShapeModel {
0.25, 0.25, 0.25, 0.75, 0.625, 0.75, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75,
// Bottom // Bottom
0.0, 0.25, 0.375, 0.25, 0.5, 0.625, 0.0, 0.25, 0.375, 0.25, 0.5, 0.625,
// Top
0.0, 0.6875, 0.0, 1.0, 1.0, 1.0
// 4 sides of hopper (top) // 4 sides of hopper (top)
//0.0, 0.6875, 0.0, 1.0, 1.0, 0.125, 0.0, 0.6875, 0.0, 1.0, 1.0, 0.125,
//0.0, 0.6875, 0.875, 1.0, 1.0, 1.0, 0.0, 0.6875, 0.875, 1.0, 1.0, 1.0,
//0.0, 0.6875, 0.0, 0.125, 1.0, 1.0, 0.0, 0.6875, 0.0, 0.125, 1.0, 1.0,
//0.875, 0.6875, 0.0, 1.0, 1.0, 1.0, 0.875, 0.6875, 0.0, 1.0, 1.0, 1.0,
}; };
case EAST: case EAST:
return new double[] { return new double[] {
@@ -88,13 +94,11 @@ public class BukkitHopper implements BukkitShapeModel {
0.25, 0.25, 0.25, 0.75, 0.625, 0.75, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75,
// Bottom // Bottom
0.75, 0.25, 0.375, 1.0, 0.5, 0.625, 0.75, 0.25, 0.375, 1.0, 0.5, 0.625,
// Top
0.0, 0.6875, 0.0, 1.0, 1.0, 1.0
// 4 sides of hopper (top) // 4 sides of hopper (top)
//0.0, 0.6875, 0.0, 1.0, 1.0, 0.125, 0.0, 0.6875, 0.0, 1.0, 1.0, 0.125,
//0.0, 0.6875, 0.875, 1.0, 1.0, 1.0, 0.0, 0.6875, 0.875, 1.0, 1.0, 1.0,
//0.0, 0.6875, 0.0, 0.125, 1.0, 1.0, 0.0, 0.6875, 0.0, 0.125, 1.0, 1.0,
//0.875, 0.6875, 0.0, 1.0, 1.0, 1.0, 0.875, 0.6875, 0.0, 1.0, 1.0, 1.0,
}; };
case DOWN: case DOWN:
return new double[] { return new double[] {
@@ -104,13 +108,11 @@ public class BukkitHopper implements BukkitShapeModel {
0.25, 0.25, 0.25, 0.75, 0.625, 0.75, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75,
// Bottom // Bottom
0.375, 0.0, 0.375, 0.625, 0.25, 0.625, 0.375, 0.0, 0.375, 0.625, 0.25, 0.625,
// Top
0.0, 0.6875, 0.0, 1.0, 1.0, 1.0
// 4 sides of hopper (top) // 4 sides of hopper (top)
//0.0, 0.6875, 0.0, 1.0, 1.0, 0.125, 0.0, 0.6875, 0.0, 1.0, 1.0, 0.125,
//0.0, 0.6875, 0.875, 1.0, 1.0, 1.0, 0.0, 0.6875, 0.875, 1.0, 1.0, 1.0,
//0.0, 0.6875, 0.0, 0.125, 1.0, 1.0, 0.0, 0.6875, 0.0, 0.125, 1.0, 1.0,
//0.875, 0.6875, 0.0, 1.0, 1.0, 1.0, 0.875, 0.6875, 0.0, 1.0, 1.0, 1.0,
}; };
default: default:
break; break;
@@ -119,8 +121,23 @@ public class BukkitHopper implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
IPlayerData pData = blockCache.getPlayerData();
if (pData != null && pData.getClientVersion().isLowerThan(ClientVersion.V_1_13)) {
return new double[] {0, 0, 0, 1, 1, 1};
}
return new double[] {0.0, 0.625, 0.0, 1.0, 1.0, 1.0, 0.25, 0.25, 0.25, 0.75, 0.625, 0.75};
//return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
}
} }
@@ -32,6 +32,16 @@ public class BukkitLadder implements BukkitShapeModel {
return new double[] {box.getMinX()-x, box.getMinY()-y, box.getMinZ()-z, box.getMaxX()-x, box.getMaxY()-y, box.getMaxZ()-z}; return new double[] {box.getMinX()-x, box.getMinY()-y, box.getMinZ()-z, box.getMaxX()-x, box.getMaxY()-y, box.getMaxZ()-z};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
@@ -38,6 +38,16 @@ public class BukkitLantern implements BukkitShapeModel {
return new double[] {xz, 0.0, xz, 1.0-xz, 0.4375, 1.0-xz, 0.375, 0.4375, 0.375, 0.625, 0.5625, 0.625}; return new double[] {xz, 0.0, xz, 1.0-xz, 0.4375, 1.0-xz, 0.375, 0.4375, 0.375, 0.625, 0.5625, 0.625};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -37,6 +37,16 @@ public class BukkitLevelled implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 8 / 9f, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 8 / 9f, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return null;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
@@ -0,0 +1,50 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
/**
* Water Lily shape model.
*/
public class BukkitLilyPad implements BukkitShapeModel {
@Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
final IPlayerData data = blockCache.getPlayerData();
if (data != null && data.getClientVersion().isLowerThan(ClientVersion.V_1_9)) {
return new double[] {0.0625, 0.0, 0.0625, 0.9375, 0.125, 0.9375};
}
return new double[] {0.0625, 0.0, 0.0625, 0.9375, 0.09375, 0.9375};
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0;
}
}
@@ -71,6 +71,16 @@ public class BukkitPiston implements BukkitShapeModel {
return res; return res;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -81,6 +81,16 @@ public class BukkitPistonHead implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
@@ -22,21 +22,32 @@ import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitPowderSnow implements BukkitShapeModel { public class BukkitPowderSnow implements BukkitShapeModel {
// Some what behave like ladder
double[] REDUCED_HEIGHT = {0.0, 0.0, 0.0, 1.0, 0.9, 1.0};
double[] FULL_HEIGHT = {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
@Override @Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) { public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
IPlayerData pData = blockCache.getPlayerData(); IPlayerData pData = blockCache.getPlayerData();
if (pData != null) { if (pData != null) {
MovingData data = pData.getGenericInstance(MovingData.class); MovingData data = pData.getGenericInstance(MovingData.class);
// TODO: Make NoFall no dealing damage on this block if (data.noFallFallDistance > 2.5) {
if (data.lastY > (y + 1 - 1e-5) && data.hasLeatherBoots && !pData.isShiftKeyPressed()) { return REDUCED_HEIGHT;
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; } else if (data.lastY > y + 1 - 1e-5 && data.hasLeatherBoots && !pData.isShiftKeyPressed()) {
return FULL_HEIGHT;
} }
// Give up, too hard to properly implement, workaround instead
//if (data.noFallFallDistance > 2.5) {
// return new double[] {0.0, 0.0, 0.0, 1.0, 0.9, 1.0};
//}
} }
return new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; return null;
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return FULL_HEIGHT;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
} }
@Override @Override
@@ -39,4 +39,15 @@ public class BukkitRail implements BukkitShapeModel {
} }
return 0; return 0;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
// TODO Hold on, this model doesn't seem right, will fix later
return null;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
}
} }
@@ -0,0 +1,123 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Scaffolding;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
/**
* Scaffolding shape model.
*/
public class BukkitScaffolding implements BukkitShapeModel {
// Stable visual shape (top slab + four posts)
private static final double[] STABLE_SHAPE = new double[] {
// top slab: 0,14,0 -> 16,16,16
0.0, 0.875, 0.0, 1.0, 1.0, 1.0,
// post (0..2 x, 0..2 z)
0.0, 0.0, 0.0, 0.125, 1.0, 0.125,
// post (14..16 x, 0..2 z)
0.875, 0.0, 0.0, 1.0, 1.0, 0.125,
// post (0..2 x, 14..16 z)
0.0, 0.0, 0.875, 0.125, 1.0, 1.0,
// post (14..16 x, 14..16 z)
0.875, 0.0, 0.875, 1.0, 1.0, 1.0
};
// Unstable visual shape includes a thin bottom plate plus the stable parts
private static final double[] UNSTABLE_SHAPE = new double[] {
// bottom full thin plate: 0,0,0 -> 16,2,16
0.0, 0.0, 0.0, 1.0, 0.125, 1.0,
// include the stable shape boxes as well
0.0, 0.875, 0.0, 1.0, 1.0, 1.0,
0.0, 0.0, 0.0, 0.125, 1.0, 0.125,
0.875, 0.0, 0.0, 1.0, 1.0, 0.125,
0.0, 0.0, 0.875, 0.125, 1.0, 1.0,
0.875, 0.0, 0.875, 1.0, 1.0, 1.0
};
// Just define ground plate, no need full boxes as Minecraft handle climbable if player within 0.3125 inset to the center, and was handled by NCP somewhere else
private static final double[] BOTTOM_PLATE = new double[] {0.0, 0.0, 0.0, 1.0, 0.125, 1.0};
private static final double[] UPPER_PLATE = new double[] {0.0, 0.875, 0.0, 1.0, 1.0, 1.0};
private static final double[] ALL_PLATE = new double[] {0.0, 0.875, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.125, 1.0};
// (empty box)
private static final double[] NO_COLLISION = null;
@Override
public double[] getShape(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z);
final BlockData data = block.getBlockData();
// If the player is above the block and not sneaking, return the stable full shape.
IPlayerData pData = blockCache.getPlayerData();
if (data instanceof Scaffolding) {
Scaffolding scaff = (Scaffolding) data;
if (pData != null) {
MovingData mData = pData.getGenericInstance(MovingData.class);
boolean hasbottom = false;
// If scaffolding has distance != 0 and is bottom, and player is just above or within tolerance return bottom plate collision
if (scaff.getDistance() != 0 && scaff.isBottom()) {
hasbottom = true;
}
// If player was above the block and not sneaking -> stable collision
// Vanilla: return full stable collision only when the entity is above the block AND not descending.
// 'isDescending' in vanilla is actually the entity's "isShiftKeyDown" (sneak) flag, not a vertical-velocity test.
//System.out.println("scf 1 " + mData.lastY + ">" + (y + 1 - 1e-5) + " " + !pData.isShiftKeyPressed() + " " + y);
if (mData.lastY > (y + 1 - 1e-5) // p_56071_.isAbove(Shapes.block(), p_56070_, true)
&& !pData.isShiftKeyPressed()) {
return hasbottom ? ALL_PLATE : UPPER_PLATE;
}
if (hasbottom && mData.lastY > y + 0.125 - 1e-5) {
//System.out.println("scf 2");
return BOTTOM_PLATE;
}
//System.out.println("scf 3");
return NO_COLLISION;
// TODO: This should be the VISUAL shape (aka: hitbox), not the COLLISION shape.
// We need to distinguish shapes from collision shapes in the API.
// return scaff.isBottom() ? UNSTABLE_SHAPE : STABLE_SHAPE;
}
// Shortcut Visible haven't set pData...
return (scaff.getDistance() != 0 && scaff.isBottom()) ? UNSTABLE_SHAPE : STABLE_SHAPE;
}
// Fallback to a full block. Should never happen.
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
}
@Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0;
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z);
final BlockData data = block.getBlockData();
if (data instanceof Scaffolding) {
Scaffolding scaff = (Scaffolding) data;
return (scaff.getDistance() != 0 && scaff.isBottom()) ? UNSTABLE_SHAPE : STABLE_SHAPE;
}
return null;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return false;
}
}
@@ -54,8 +54,18 @@ public class BukkitSeaPickle implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -16,30 +16,87 @@ package fr.neatmonster.nocheatplus.compat.bukkit.model;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.block.Container; import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitShulkerBox implements BukkitShapeModel { public class BukkitShulkerBox implements BukkitShapeModel {
double FULL_BLOCK[] = {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
@Override @Override
public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final BlockState state = block.getState(); final BlockState state = block.getState();
//final BlockData blockData = state.getBlockData();
if (state instanceof Container) { // Base full block
if (!((Container) state).getInventory().getViewers().isEmpty()) { double minX = 0.0, minY = 0.0, minZ = 0.0;
return new double[] {0.0, 0.0, 0.0, 1.0, 1.5, 1.0}; double maxX = 1.0, maxY = 1.0, maxZ = 1.0;
if ((state instanceof ShulkerBox)) {
final ShulkerBox shulker = (ShulkerBox) state;
// Closed shulker. Return full block
if (shulker.getInventory().getViewers().isEmpty()) {
return FULL_BLOCK;
} }
// Facing is stored in BlockData, not in ShulkerBox
BlockFace face = BlockFace.UP;
BlockData data = block.getBlockData();
if (data instanceof Directional) {
face = ((Directional) data).getFacing();
}
// Open shulker: extend 0.5 blocks in facing direction
switch (face) {
case UP:
maxY = 1.5;
break;
case DOWN:
minY = -0.5;
break;
case NORTH:
minZ = -0.5;
break;
case SOUTH:
maxZ = 1.5;
break;
case WEST:
minX = -0.5;
break;
case EAST:
maxX = 1.5;
break;
default:
// Should not happen, but just in case... Treat as UP
maxY = 1.5;
break;
}
} }
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
return new double[]{minX, minY, minZ, maxX, maxY, maxZ};
}
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
} }
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
} }
@@ -46,6 +46,16 @@ public class BukkitSlab implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -25,7 +25,7 @@ import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitSnow implements BukkitShapeModel { public class BukkitSnow implements BukkitShapeModel {
private static final double[][] SNOW_LAYERS = { private static final double[][] SNOW_LAYERS = {
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, null,
{0.0, 0.0, 0.0, 1.0, 0.125, 1.0}, {0.0, 0.0, 0.0, 1.0, 0.125, 1.0},
{0.0, 0.0, 0.0, 1.0, 0.250, 1.0}, {0.0, 0.0, 0.0, 1.0, 0.250, 1.0},
{0.0, 0.0, 0.0, 1.0, 0.375, 1.0}, {0.0, 0.0, 0.0, 1.0, 0.375, 1.0},
@@ -48,6 +48,16 @@ public class BukkitSnow implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
@@ -14,8 +14,6 @@
*/ */
package fr.neatmonster.nocheatplus.compat.bukkit.model; package fr.neatmonster.nocheatplus.compat.bukkit.model;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.bukkit.World; import org.bukkit.World;
@@ -25,8 +23,7 @@ import org.bukkit.block.data.Bisected.Half;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Stairs; import org.bukkit.block.data.type.Stairs;
import fr.neatmonster.nocheatplus.utilities.collision.Axis; import fr.neatmonster.nocheatplus.utilities.collision.ShapeUtils;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitStairs implements BukkitShapeModel { public class BukkitStairs implements BukkitShapeModel {
@@ -66,6 +63,16 @@ public class BukkitStairs implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
//final Block block = world.getBlockAt(x, y, z); //final Block block = world.getBlockAt(x, y, z);
@@ -95,16 +102,16 @@ public class BukkitStairs implements BukkitShapeModel {
private double[] makeStairShape(int flags, double[] slab, double[] octet_nn, double[] octet_pn, double[] octet_np, double[] octet_pp) { private double[] makeStairShape(int flags, double[] slab, double[] octet_nn, double[] octet_pn, double[] octet_np, double[] octet_pp) {
double[] res = slab; double[] res = slab;
if ((flags & 1) != 0) { if ((flags & 1) != 0) {
res = merge(res, octet_nn); res = ShapeUtils.merge(res, octet_nn);
} }
if ((flags & 2) != 0) { if ((flags & 2) != 0) {
res = merge(res, octet_pn); res = ShapeUtils.merge(res, octet_pn);
} }
if ((flags & 4) != 0) { if ((flags & 4) != 0) {
res = merge(res, octet_np); res = ShapeUtils.merge(res, octet_np);
} }
if ((flags & 8) != 0) { if ((flags & 8) != 0) {
res = merge(res, octet_pp); res = ShapeUtils.merge(res, octet_pp);
} }
return res; return res;
} }
@@ -130,77 +137,5 @@ public class BukkitStairs implements BukkitShapeModel {
} }
} }
// TODO: Poorly designed, Will recode better version later
private double[] merge(double[] bounds, double[] octet) {
double[] res = bounds;
final double minX = octet[0];
final double minY = octet[1];
final double minZ = octet[2];
final double maxX = octet[3];
final double maxY = octet[4];
final double maxZ = octet[5];
for (int i = 2; i <= AxisAlignedBBUtils.getNumberOfAABBs(bounds); i++) {
final double tminX = bounds[i*6-6];
final double tminY = bounds[i*6-5];
final double tminZ = bounds[i*6-4];
final double tmaxX = bounds[i*6-3];
final double tmaxY = bounds[i*6-2];
final double tmaxZ = bounds[i*6-1];
if (sameshape(minX, minY, minZ, maxX, maxY, maxZ, tminX, tminY, tminZ, tmaxX, tmaxY, tmaxZ)) {
final List<Axis> a = getRelative(minX, minY, minZ, maxX, maxY, maxZ, tminX, tminY, tminZ, tmaxX, tmaxY, tmaxZ);
if (a.size() == 1) {
Axis axis = a.get(0);
switch (axis) {
case X_AXIS:
res[i*6-6] = Math.min(tminX, minX);
res[i*6-3] = Math.max(tmaxX, maxX);
return res;
//case Y_AXIS:
// break;
case Z_AXIS:
res[i*6-4] = Math.min(tminZ, minZ);
res[i*6-1] = Math.max(tmaxZ, maxZ);
return res;
default:
break;
}
}
}
}
return add(res, octet);
}
private double[] add(final double[] array1, final double[] array2) {
final double[] newArray = new double[array1.length + array2.length];
System.arraycopy(array1, 0, newArray, 0, array1.length);
System.arraycopy(array2, 0, newArray, array1.length, array2.length);
return newArray;
}
private List<Axis> getRelative(double minX, double minY, double minZ, double maxX, double maxY, double maxZ,
double tminX, double tminY, double tminZ, double tmaxX, double tmaxY, double tmaxZ) {
final List<Axis> list = new ArrayList<Axis>();
if (minX == tmaxX || maxX == tminX) {
list.add(Axis.X_AXIS);
}
if (minY == tmaxY || maxY == tminY) {
list.add(Axis.Y_AXIS);
}
if (minZ == tmaxZ || maxZ == tminZ) {
list.add(Axis.Z_AXIS);
}
return list;
}
private boolean sameshape(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, double tminX,
double tminY, double tminZ, double tmaxX, double tmaxY, double tmaxZ) {
final double dx = maxX - minX;
final double dy = maxY - minY;
final double dz = maxZ - minZ;
final double tdx = tmaxX - tminX;
final double tdy = tmaxY - tminY;
final double tdz = tmaxZ - tminZ;
return dx == tdx && dy == tdy && dz == tdz;
}
} }
@@ -23,27 +23,34 @@ public class BukkitStatic implements BukkitShapeModel {
private final double[] bounds; private final double[] bounds;
/** /**
* Initialize with the given height and with full xz-bounds. * Constructs a {@code BukkitStatic} block model with full width (no inset) and the specified height.
* This is typically used for regular blocks that occupy the entire block space in the X and Z axes.
* *
* @param height * @param height the height of the block, corresponding to the Y axis
*/ */
public BukkitStatic(double height) { public BukkitStatic(double height) {
this(0.0, height); this(0.0, height);
} }
/** /**
* Initialize with the given height and xz-inset. * Constructs a {@code BukkitStatic} block model with a specified inset on the X and Z axes and the given height.
* This is useful for blocks that do not occupy the full width and height of a block.
* *
* @param xzInset * @param xzInset the inset on the X and Z axes, reducing the block's width and height
* @param height * @param height the height of the block, corresponding to the Y axis
*/ */
public BukkitStatic(double xzInset, double height) { public BukkitStatic(double xzInset, double height) {
this(xzInset, 0.0, xzInset, 1.0 - xzInset, height, 1.0 - xzInset); this(xzInset, 0.0, xzInset, 1.0 - xzInset, height, 1.0 - xzInset);
} }
/** /**
* Initialize with the given bounds * Constructs a {@code BukkitStatic} block model using explicit bounding box values.
* The {@code bounds} array must have a length that is a multiple of six, where each group of six values
* represents the minimum and maximum coordinates for X, Y, and Z ({@code minX, minY, minZ, maxX, maxY, maxZ}).
* This constructor allows for the definition of complex or custom-shaped blocks.
* *
* @param bounds the bounding box values, in groups of six for each box: minX, minY, minZ, maxX, maxY, maxZ
* @throws IllegalArgumentException if the length of {@code bounds} is not a multiple of six
*/ */
public BukkitStatic(double ...bounds) { public BukkitStatic(double ...bounds) {
if (bounds.length % 6 != 0) { if (bounds.length % 6 != 0) {
@@ -58,6 +65,16 @@ public class BukkitStatic implements BukkitShapeModel {
return bounds; return bounds;
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -55,6 +55,16 @@ public class BukkitTrapDoor implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
@@ -52,6 +52,16 @@ public class BukkitTurtleEgg implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -23,132 +23,184 @@ import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.MultipleFacing; import org.bukkit.block.data.MultipleFacing;
import org.bukkit.block.data.type.Wall; import org.bukkit.block.data.type.Wall;
import org.bukkit.util.BoundingBox;
import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
import fr.neatmonster.nocheatplus.utilities.collision.ShapeUtils;
import fr.neatmonster.nocheatplus.utilities.map.BlockCache; import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public class BukkitWall implements BukkitShapeModel { public class BukkitWall implements BukkitShapeModel {
private final double minXZ;
private final double maxXZ;
private final double height;
private final double sideInset;
private final double[] east; private final double[] east;
private final double[] north; private final double[] north;
private final double[] west; private final double[] west;
private final double[] south; private final double[] south;
private final double[] eastB;
private final double[] northB;
private final double[] westB;
private final double[] southB;
private final double[] eastwest; private final double[] eastwest;
private final double[] southnorth; private final double[] southnorth;
private final double[] baseState;
public BukkitWall(double inset, double height) { public BukkitWall(double inset, double height) {
this(inset, 1.0 - inset, height, 0.0); this(inset, height, inset);
} }
public BukkitWall(double inset, double height, double sideInset) { public BukkitWall(double inset, double height, double sideInset) {
this(inset, 1.0 - inset, height, sideInset); this(inset, 1.0 - inset, height, sideInset, 1.0 - sideInset);
} }
public BukkitWall(double minXZ, double maxXZ, double height, double sideInset) { public BukkitWall(double minXZ, double maxXZ, double height, double minSideXZ, double maxSideXZ) {
this.minXZ = minXZ; east = new double[] {maxXZ, 0.0, minSideXZ, 1.0, height, maxSideXZ};
this.maxXZ = maxXZ; north = new double[] {minSideXZ, 0.0, 0.0, maxSideXZ, height, minXZ};
this.height = height; west = new double[] {0.0, 0.0, minSideXZ, minXZ, height, maxSideXZ};
this.sideInset = sideInset; south = new double[] {minSideXZ, 0.0, maxXZ, maxSideXZ, height, 1.0};
east = new double[] {maxXZ, 0.0, sideInset, 1.0, height, 1.0 - sideInset}; eastB = new double[] {maxXZ, 0.0, minXZ, 1.0, height, maxXZ};
north = new double[] {sideInset, 0.0, 0.0, 1.0 - sideInset, height, minXZ}; northB = new double[] {minXZ, 0.0, 0.0, maxXZ, height, minXZ};
west = new double[] {0.0, 0.0, sideInset, minXZ, height, 1.0 - sideInset}; westB = new double[] {0.0, 0.0, minXZ, minXZ, height, maxXZ};
south = new double[] {sideInset, 0.0, maxXZ, 1.0 - sideInset, height, 1.0}; southB = new double[] {minXZ, 0.0, maxXZ, maxXZ, height, 1.0};
eastwest = new double[] {0.0, 0.0, sideInset, 1.0, height, 1.0 - sideInset}; eastwest = new double[] {0.0, 0.0, minSideXZ, 1.0, height, maxSideXZ};
southnorth = new double[] {sideInset, 0.0, 0.0, 1.0 - sideInset, height, 1.0}; southnorth = new double[] {minSideXZ, 0.0, 0.0, maxSideXZ, height, 1.0};
baseState = new double[] {minXZ, 0.0, minXZ, maxXZ, height, maxXZ};
} }
@Override @Override
public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public double[] getShape(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
// Relevant: https://bugs.mojang.com/browse/MC-9565 // Relevant: https://bugs.mojang.com/browse/MC-9565
final Block block = world.getBlockAt(x, y, z); final Block block = world.getBlockAt(x, y, z);
final BlockState state = block.getState(); final IPlayerData data = blockCache.getPlayerData();
final BlockData blockData = state.getBlockData(); final boolean legacy = data != null && data.getClientVersion().isLowerThan(ClientVersion.V_1_13);
if (Bridge1_13.hasBuiltInRayTracing() && !legacy) {
if (blockData instanceof MultipleFacing) { double[] res = {};
MultipleFacing fence = (MultipleFacing) blockData; for (BoundingBox box : block.getCollisionShape().getBoundingBoxes()) {
double[] res = new double[] {minXZ, 0.0, minXZ, maxXZ, height, maxXZ}; res = ShapeUtils.add(res, AxisAlignedBBUtils.toArray(box));
Set<BlockFace> faces = fence.getFaces();
if (!faces.contains(BlockFace.UP) && faces.size() == 2) {
if (faces.contains(BlockFace.SOUTH)) {
return new double[] {sideInset, 0.0, 0.0, 1.0 - sideInset, height, 1.0};
}
if (faces.contains(BlockFace.WEST)) {
return new double[] {0.0, 0.0, sideInset, 1.0, height, 1.0 - sideInset};
}
}
for (final BlockFace face : fence.getFaces()) {
switch (face) {
case EAST:
res = add(res, east);
break;
case NORTH:
res = add(res, north);
break;
case WEST:
res = add(res, west);
break;
case SOUTH:
res = add(res, south);
break;
default:
break;
}
} }
if (res.length == 0) return null;
return res; return res;
} } else {
else if (blockData instanceof Wall) { final BlockState state = block.getState();
final Wall wall = (Wall) blockData; final BlockData blockData = state.getBlockData();
if (wall.isUp()) { boolean east = false;
double[] res = new double[] {minXZ, 0.0, minXZ, maxXZ, height, maxXZ}; boolean north = false;
boolean west = false;
boolean south = false;
boolean up = false;
if (blockData instanceof MultipleFacing) {
MultipleFacing fence = (MultipleFacing) blockData;
Set<BlockFace> faces = fence.getFaces();
up = faces.contains(BlockFace.UP);
for (final BlockFace face : fence.getFaces()) {
switch (face) {
case EAST:
east = true;
break;
case NORTH:
north = true;
break;
case WEST:
west = true;
break;
case SOUTH:
south = true;
break;
default:
break;
}
}
}
else if (blockData instanceof Wall) {
final Wall wall = (Wall) blockData;
up = wall.isUp();
if (!wall.getHeight(BlockFace.WEST).equals(Wall.Height.NONE)) { if (!wall.getHeight(BlockFace.WEST).equals(Wall.Height.NONE)) {
res = add(res, west); west = true;
} }
if (!wall.getHeight(BlockFace.EAST).equals(Wall.Height.NONE)) { if (!wall.getHeight(BlockFace.EAST).equals(Wall.Height.NONE)) {
res = add(res, east); east = true;
} }
if (!wall.getHeight(BlockFace.NORTH).equals(Wall.Height.NONE)) { if (!wall.getHeight(BlockFace.NORTH).equals(Wall.Height.NONE)) {
res = add(res, north); north = true;
} }
if (!wall.getHeight(BlockFace.SOUTH).equals(Wall.Height.NONE)) { if (!wall.getHeight(BlockFace.SOUTH).equals(Wall.Height.NONE)) {
res = add(res, south); south = true;
}
return res;
}
else {
double[] res = null;
if (!wall.getHeight(BlockFace.WEST).equals(Wall.Height.NONE)
&& !wall.getHeight(BlockFace.EAST).equals(Wall.Height.NONE)) {
if (res == null) {
res = eastwest;
}
}
if (!wall.getHeight(BlockFace.NORTH).equals(Wall.Height.NONE)
&& !wall.getHeight(BlockFace.SOUTH).equals(Wall.Height.NONE)) {
if (res == null) {
res = southnorth;
}
else res = add(res, southnorth);
}
if (res != null) {
return res;
} }
} }
if (legacy && east && west && south && north) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.5, 1.0};
}
double tmp[] = new double[0];
if (east && west) {
if (legacy) {
if (south) {
return new double[] {0.0, 0.0, 0.25, 1.0, 1.5, 1.0};
}
if (north) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.5, 0.75};
}
}
tmp = eastwest;
east = west = false;
} else if (south && north) {
if (legacy) {
if (west) {
return new double[] {0.0, 0.0, 0.0, 0.75, 1.5, 1.0};
}
if (east) {
return new double[] {0.25, 0.0, 0.0, 1.0, 1.5, 1.0};
}
}
tmp = southnorth;
south = north = false;
}
if (legacy) {
if (south && west) {return new double[] {0.0, 0.0, 0.25, 0.75, 1.5, 1.0};}
if (south && east) {return new double[] {0.25, 0.0, 0.25, 1.0, 1.5, 1.0};}
if (north && west) {return new double[] {0.0, 0.0, 0.0, 0.75, 1.5, 0.75};}
if (north && east) {return new double[] {0.25, 0.0, 0.0, 1.0, 1.5, 0.75};}
}
if (south) {
tmp = ShapeUtils.add(tmp, legacy ? this.southB : this.south);
}
if (north) {
tmp = ShapeUtils.add(tmp, legacy ? this.northB : this.north);
}
if (east) {
tmp = ShapeUtils.add(tmp, legacy ? this.eastB : this.east);
}
if (west) {
tmp = ShapeUtils.add(tmp, legacy ? this.westB : this.west);
}
if (tmp.length == 0 || up) {
tmp = ShapeUtils.add(tmp, this.baseState);
}
return tmp;
} }
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
double a[] = getShape(blockCache, world, x, y, z);
for (int i = 1; i < (int)a.length / 6; i++) a[i*6-2] = 1.0;
return a;
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
// TODO: Which one is better, return true and cap the height or return false and fetch twice?
// NOTE: Should be no different as current implementation doesn't do exact ray trace check
return true;
} }
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
} }
private double[] add(final double[] array1, final double[] array2) {
final double[] newArray = new double[array1.length + array2.length];
System.arraycopy(array1, 0, newArray, 0, array1.length);
System.arraycopy(array2, 0, newArray, array1.length, array2.length);
return newArray;
}
} }
@@ -47,6 +47,16 @@ public class BukkitWallHead implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) { public int getFakeData(final BlockCache blockCache, final World world, final int x, final int y, final int z) {
return 0; return 0;
@@ -33,6 +33,16 @@ public class BukkitWaterPlant implements BukkitShapeModel {
return new double[] {0.0, 0.0, 0.0, 1.0, 8 / 9f, 1.0}; return new double[] {0.0, 0.0, 0.0, 1.0, 8 / 9f, 1.0};
} }
@Override
public double[] getVisualShape(BlockCache blockCache, World world, int x, int y, int z) {
return getShape(blockCache, world, x, y, z);
}
@Override
public boolean isCollisionSameVisual(BlockCache blockCache, World world, int x, int y, int z) {
return true;
}
@Override @Override
public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) { public int getFakeData(BlockCache blockCache, World world, int x, int y, int z) {
return 0; return 0;
@@ -19,11 +19,52 @@ import fr.neatmonster.nocheatplus.utilities.map.BlockCache;
public interface ShapeModel<W> { public interface ShapeModel<W> {
// TODO: Rather fill in all into node directly (data as well), avoid redundant casting etc. // TODO: Rather fill in all into node directly (data as well), avoid redundant casting etc.
// TODO: Best route passable workaround through here too (base on a flag), + getGroundMinHeight?.
// TODO: Refine +- might have BukkitBlockCacheNode etc. // TODO: Refine +- might have BukkitBlockCacheNode etc.
/**
* Get the <i>collision</i> shape of the block at the given position. <br>
* <strong> Do note that not all blocks have a collision shape, like all plants/farmable blocks, which do have a visual shape that can be interacted with, but don't have a collision one;
* other have a collision shape that exceeds their visual one, like fences, which have a height of 1.0 block visually, but 1.5 collision-wise and such. </strong> <br>
* Most of the time, the collision shape will be the same as its visual shape, but not always. <br>
* To check for any divergence, use {@link #isCollisionSameVisual(BlockCache, W, int, int, int)}. <br>
*
* @param blockCache The block cache.
* @param world The world.
* @param x The x coordinate of the block.
* @param y The y coordinate of the block.
* @param z The z coordinate of the block.
* @return An array of doubles representing the collision shape, in the format
* {minX, minY, minZ, maxX, maxY, maxZ, ...} for each box in the shape.
* Coordinates are in the range [0.0, 1.0].
*/
public double[] getShape(BlockCache blockCache, W world, int x, int y, int z); public double[] getShape(BlockCache blockCache, W world, int x, int y, int z);
/**
* Tell if collision shape is the same as visual shape <br>
*
* @param blockCache
* @param world
* @param x
* @param y
* @param z
* @return if true
*/
public boolean isCollisionSameVisual(BlockCache blockCache, W world, int x, int y, int z);
/**
* Get the visual shape of the block at the given position. <br>
* Call this method if dealing with visual interactions, like ray tracing for line of sight, block targeting, etc. <br>
* @See {@link #getShape(BlockCache, W, int, int, int)} for collision shape. <br>
*
* @param blockCache
* @param world
* @param x
* @param y
* @param z
* @return
*/
public double[] getVisualShape(BlockCache blockCache, W world, int x, int y, int z);
/** /**
* Allow faking data. * Allow faking data.
* *
@@ -49,7 +49,26 @@ public class BlockCacheCBReflect extends BlockCacheBukkit {
final double[] shape = LegacyBlocks.getShape(this, mat, x, y, z, true); final double[] shape = LegacyBlocks.getShape(this, mat, x, y, z, true);
if (shape != null) return shape; if (shape != null) return shape;
try { try {
return helper.nmsWorld_fetchBlockShape(this.nmsWorld, this.getType(x, y, z), x, y, z); return LegacyBlocks.adjustBounds(this, mat, x, y, z, helper.nmsWorld_fetchBlockShape(this.nmsWorld, this.getType(x, y, z), x, y, z));
}
catch (ReflectFailureException ex) {
return super.fetchBounds(x, y, z);
}
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final org.bukkit.Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(int x, int y, int z) {
final org.bukkit.Material mat = getType(x, y, z);
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
try {
return LegacyBlocks.adjustBounds(this, mat, x, y, z, helper.nmsWorld_fetchBlockShape(this.nmsWorld, this.getType(x, y, z), x, y, z));
} }
catch (ReflectFailureException ex) { catch (ReflectFailureException ex) {
return super.fetchBounds(x, y, z); return super.fetchBounds(x, y, z);
@@ -20,9 +20,13 @@ import java.util.UUID;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.compat.AttribUtil; import fr.neatmonster.nocheatplus.compat.AttribUtil;
import fr.neatmonster.nocheatplus.components.modifier.IAttributeAccess; import fr.neatmonster.nocheatplus.components.modifier.IAttributeAccess;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil; import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
import fr.neatmonster.nocheatplus.utilities.map.MaterialUtil;
import fr.neatmonster.nocheatplus.utilities.moving.Magic;
public class ReflectAttributeAccess implements IAttributeAccess { public class ReflectAttributeAccess implements IAttributeAccess {
@@ -216,71 +220,78 @@ public class ReflectAttributeAccess implements IAttributeAccess {
@Override @Override
public double getGravity(Player player) { public double getGravity(Player player) {
return Double.MAX_VALUE; return Magic.DEFAULT_GRAVITY;
} }
@Override @Override
public double getSafeFallDistance(Player player) { public double getSafeFallDistance(Player player) {
return Double.MAX_VALUE; return Magic.FALL_DAMAGE_DIST;
} }
@Override @Override
public double getFallDamageMultiplier(Player player) { public double getFallDamageMultiplier(Player player) {
return Double.MAX_VALUE; return 1.0;
} }
@Override @Override
public double getBreakingSpeedMultiplier(Player player) { public double getBreakingSpeedMultiplier(Player player) {
return Double.MAX_VALUE; return 1.0;
} }
@Override @Override
public double getJumpGainMultiplier(Player player) { public double getJumpGainMultiplier(Player player) {
return Double.MAX_VALUE; return 1.0;
} }
@Override @Override
public double getPlayerSneakingFactor(Player player) { public double getPlayerSneakingFactor(Player player) {
return Double.MAX_VALUE; return Magic.SNEAK_MULTIPLIER;
} }
@Override @Override
public double getPlayerMaxBlockReach(Player player) { public double getPlayerMaxBlockReach(Player player) {
return Double.MAX_VALUE; return 4.5;
} }
@Override @Override
public double getPlayerMaxAttackReach(Player player) { public double getPlayerMaxAttackReach(Player player) {
return Double.MAX_VALUE; return 3.0;
} }
@Override @Override
public double getMaxStepUp(Player player) { public double getMaxStepUp(Player player) {
return Double.MAX_VALUE; if (player.isInsideVehicle()) {
if (player.getVehicle() != null && MaterialUtil.isBoat(player.getVehicle().getType())) {
return 0.0;
}
return 1.0;
}
final MovingConfig cc = DataManager.getPlayerData(player).getGenericInstance(MovingConfig.class);
return cc.sfStepHeight;
} }
@Override @Override
public float getMovementEfficiency(Player player) { public float getMovementEfficiency(Player player) {
return Float.MAX_VALUE; return 0f;
} }
@Override @Override
public float getWaterMovementEfficiency(Player player) { public float getWaterMovementEfficiency(Player player) {
return Float.MAX_VALUE; return 0f;
} }
@Override @Override
public double getSubmergedMiningSpeedMultiplier(Player player) { public double getSubmergedMiningSpeedMultiplier(Player player) {
return Double.MAX_VALUE; return 1.0;
} }
@Override @Override
public double getMiningEfficiency(Player player) { public double getMiningEfficiency(Player player) {
return Double.MAX_VALUE; return 0f;
} }
@Override @Override
public double getEntityScale(Player player) { public double getEntityScale(Player player) {
return Double.MAX_VALUE; return 1.0;
} }
} }
@@ -60,6 +60,13 @@ public class ReflectDamageSources {
nmsDamageSources = ReflectionUtil.getMethodNoArgs(nmsEntityClass, "dX", nmsClass); nmsDamageSources = ReflectionUtil.getMethodNoArgs(nmsEntityClass, "dX", nmsClass);
nmsfall = ReflectionUtil.getMethodNoArgs(nmsClass, "l", reflectDamageSource.nmsClass); nmsfall = ReflectionUtil.getMethodNoArgs(nmsClass, "l", reflectDamageSource.nmsClass);
} }
// 1.21.8 and possibly earlier down to .4
if (nmsDamageSources == null) {
nmsDamageSources = ReflectionUtil.getMethodNoArgs(nmsEntityClass, "ea", nmsClass);
// TODO: Check
// nmsfall = ReflectionUtil.getMethodNoArgs(nmsClass, "l", reflectDamageSource.nmsClass);
}
} }
public Object getDamageSource(Object handle) { public Object getDamageSource(Object handle) {
@@ -86,7 +86,8 @@ public class ReflectEntity extends ReflectGetHandleBase<Entity> {
} }
private Method seekClearActiveItemMethod(Class<?> nmsClass) { private Method seekClearActiveItemMethod(Class<?> nmsClass) {
// void releaseUsingItem() -> fE(1.21.2-1.21.3) // void releaseUsingItem() -> fL (1.21.8 and possibly down to .4)
// fE(1.21.2-1.21.3)
// fw(1.21-1.21.1) // fw(1.21-1.21.1)
// fA(1.20.5-1.20.6) // fA(1.20.5-1.20.6)
// fs(1.20.4) // fs(1.20.4)
@@ -95,7 +96,7 @@ public class ReflectEntity extends ReflectGetHandleBase<Entity> {
// fj(1.19-19.4) // fj(1.19-19.4)
// eR(???) // eR(???)
// clearActiveItem(1.13-???) // clearActiveItem(1.13-???)
String[] methodname = {"fE", "fw", "fA", "fs", "fr", "fn", "fj", "eR", "clearActiveItem", "releaseUsingItem"}; String[] methodname = {"fL", "fE", "fw", "fA", "fs", "fr", "fn", "fj", "eR", "clearActiveItem", "releaseUsingItem"};
for (String name : methodname) { for (String name : methodname) {
Method method = ReflectionUtil.getMethodNoArgs(nmsClass, name); Method method = ReflectionUtil.getMethodNoArgs(nmsClass, name);
if (method != null) return method; if (method != null) return method;
@@ -109,7 +109,29 @@ public class BlockCacheCB2922 extends BlockCache {
block.updateShape(iBlockAccess, x, y, z); // data from cache. block.updateShape(iBlockAccess, x, y, z); // data from cache.
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
// TODO: change api for this / use nodes (!)
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_7_R1.Block block = net.minecraft.server.v1_7_R1.Block.e(id);
if (block == null) return null;
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(iBlockAccess, x, y, z); // data from cache.
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
} }
@Override @Override
@@ -156,4 +178,8 @@ public class BlockCacheCB2922 extends BlockCache {
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -104,7 +104,29 @@ public class BlockCacheCB3026 extends BlockCache {
block.updateShape(iBlockAccess, x, y, z); // getData from cache. block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
// TODO: change api for this / use nodes (!)
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_7_R2.Block block = net.minecraft.server.v1_7_R2.Block.e(id);
if (block == null) return null;
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
} }
@Override @Override
@@ -150,5 +172,8 @@ public class BlockCacheCB3026 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -104,7 +104,29 @@ public class BlockCacheCB3043 extends BlockCache {
block.updateShape(iBlockAccess, x, y, z); // getData from cache. block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
// TODO: change api for this / use nodes (!)
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_7_R3.Block block = net.minecraft.server.v1_7_R3.Block.e(id);
if (block == null) return null;
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
} }
@Override @Override
@@ -150,5 +172,8 @@ public class BlockCacheCB3043 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -105,7 +105,31 @@ public class BlockCacheCB3100 extends BlockCache {
block.updateShape(iBlockAccess, x, y, z); // getData from cache. block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_7_R4.Block block = net.minecraft.server.v1_7_R4.Block.getById(id);
if (block == null) {
// TODO: Convention for null bounds -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(iBlockAccess, x, y, z); // getData from cache.
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.x(), block.z(), block.B(), block.y(), block.A(), block.C()});
} }
@Override @Override
@@ -153,5 +177,8 @@ public class BlockCacheCB3100 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -85,7 +85,37 @@ public class BlockCacheSpigotCB1_10_R1 extends BlockCache {
//return null; //return null;
} }
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_10_R1.Block block = net.minecraft.server.v1_10_R1.Block.getById(id);
if (block == null) {
// TODO: Convention for null blocks -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
final BlockPosition pos = new BlockPosition(x, y, z);
// TODO: Deprecation warning below (reason / substitute?).
@SuppressWarnings("deprecation")
final AxisAlignedBB bb = block.a(world.getType(pos), world, pos);
if (bb == null) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // Special case.
//return null;
}
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
} }
@Override @Override
@@ -133,5 +163,8 @@ public class BlockCacheSpigotCB1_10_R1 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -115,7 +115,37 @@ public class BlockCacheSpigotCB1_11_R1 extends BlockCache {
//return null; //return null;
} }
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_11_R1.Block block = net.minecraft.server.v1_11_R1.Block.getById(id);
if (block == null) {
// TODO: Convention for null blocks -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
final BlockPosition pos = new BlockPosition(x, y, z);
// TODO: Deprecation warning below (reason / substitute?).
@SuppressWarnings("deprecation")
final AxisAlignedBB bb = block.b(iBlockAccess.getType(pos), iBlockAccess, pos);
if (bb == null) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // Special case.
//return null;
}
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
} }
@Override @Override
@@ -163,5 +193,8 @@ public class BlockCacheSpigotCB1_11_R1 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -85,7 +85,37 @@ public class BlockCacheSpigotCB1_12_R1 extends BlockCache {
//return null; //return null;
} }
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_12_R1.Block block = net.minecraft.server.v1_12_R1.Block.getById(id);
if (block == null) {
// TODO: Convention for null blocks -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
final BlockPosition pos = new BlockPosition(x, y, z);
// TODO: Deprecation warning below (reason / substitute?).
@SuppressWarnings("deprecation")
final AxisAlignedBB bb = block.b(world.getType(pos), world, pos);
if (bb == null) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // Special case.
//return null;
}
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
} }
@Override @Override
@@ -133,5 +163,8 @@ public class BlockCacheSpigotCB1_12_R1 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -78,7 +78,31 @@ public class BlockCacheSpigotCB1_8_R1 extends BlockCache {
block.updateShape(world, new BlockPosition(x, y, z)); block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.z(), block.B(), block.D(), block.A(), block.C(), block.E()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.z(), block.B(), block.D(), block.A(), block.C(), block.E()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_8_R1.Block block = net.minecraft.server.v1_8_R1.Block.getById(id);
if (block == null) {
// TODO: Convention for null bounds -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.z(), block.B(), block.D(), block.A(), block.C(), block.E()});
} }
@Override @Override
@@ -126,5 +150,8 @@ public class BlockCacheSpigotCB1_8_R1 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -78,7 +78,31 @@ public class BlockCacheSpigotCB1_8_R2 extends BlockCache {
block.updateShape(world, new BlockPosition(x, y, z)); block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_8_R2.Block block = net.minecraft.server.v1_8_R2.Block.getById(id);
if (block == null) {
// TODO: Convention for null bounds -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()});
} }
@Override @Override
@@ -126,5 +150,8 @@ public class BlockCacheSpigotCB1_8_R2 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -78,7 +78,31 @@ public class BlockCacheSpigotCB1_8_R3 extends BlockCache {
block.updateShape(world, new BlockPosition(x, y, z)); block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_8_R3.Block block = net.minecraft.server.v1_8_R3.Block.getById(id);
if (block == null) {
// TODO: Convention for null bounds -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
block.updateShape(world, new BlockPosition(x, y, z));
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{block.B(), block.D(), block.F(), block.C(), block.E(), block.G()});
} }
@Override @Override
@@ -126,5 +150,8 @@ public class BlockCacheSpigotCB1_8_R3 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -83,7 +83,35 @@ public class BlockCacheSpigotCB1_9_R1 extends BlockCache {
//return null; //return null;
} }
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_9_R1.Block block = net.minecraft.server.v1_9_R1.Block.getById(id);
if (block == null) {
// TODO: Convention for null blocks -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
final BlockPosition pos = new BlockPosition(x, y, z);
final AxisAlignedBB bb = block.a(world.getType(pos), world, pos);
if (bb == null) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // Special case.
//return null;
}
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
} }
@Override @Override
@@ -131,5 +159,8 @@ public class BlockCacheSpigotCB1_9_R1 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
@@ -85,7 +85,37 @@ public class BlockCacheSpigotCB1_9_R2 extends BlockCache {
//return null; //return null;
} }
// minX, minY, minZ, maxX, maxY, maxZ // minX, minY, minZ, maxX, maxY, maxZ
return new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f}; return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
}
@Override
public boolean isCollisionSameVisual(int x, int y, int z) {
final Material mat = getType(x, y, z);
return LegacyBlocks.isCollisionSameVisual(this, mat, x, y, z);
}
@Override
public double[] fetchVisualBounds(final int x, final int y, final int z){
final Material mat = getType(x, y, z);
@SuppressWarnings("deprecation")
final int id = mat.getId();
final net.minecraft.server.v1_9_R2.Block block = net.minecraft.server.v1_9_R2.Block.getById(id);
if (block == null) {
// TODO: Convention for null blocks -> full ?
return null;
}
final double[] shape = LegacyBlocks.getVisualShape(this, mat, x, y, z);
if (shape != null) return shape;
final BlockPosition pos = new BlockPosition(x, y, z);
// TODO: Deprecation warning below (reason / substitute?).
@SuppressWarnings("deprecation")
final AxisAlignedBB bb = block.a(world.getType(pos), world, pos);
if (bb == null) {
return new double[] {0.0, 0.0, 0.0, 1.0, 1.0, 1.0}; // Special case.
//return null;
}
// minX, minY, minZ, maxX, maxY, maxZ
return LegacyBlocks.adjustBounds(this, mat, x, y, z, new double[]{bb.a, bb.b, bb.c, bb.d, bb.e, bb.f});
} }
@Override @Override
@@ -133,5 +163,8 @@ public class BlockCacheSpigotCB1_9_R2 extends BlockCache {
world = null; world = null;
bukkitWorld = null; bukkitWorld = null;
} }
@Override
public long fetchExtendedData(int x, int y, int z) {
return 0;
}
} }
+5 -5
View File
@@ -20,8 +20,8 @@
<repositories> <repositories>
<repository> <repository>
<id>dmulloy2-repo</id> <id>dmulloy2-public</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url> <url>https://repo.dmulloy2.net/repository/public/</url>
</repository> </repository>
</repositories> </repositories>
@@ -29,7 +29,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version> <version>26.3-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -39,9 +39,9 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.comphenix.protocol</groupId> <groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>5.3.0</version> <version>5.5.0-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -0,0 +1,869 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.net.entity;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.UUID;
import org.bukkit.entity.EntityType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.WrappedDataValue;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
/**
* ProtocolLib field layout for the entity packets the tracker follows.
* Relative deltas are left in their packet units (1/32 before 1.9, 1/4096 after).
*/
public final class EntityPacketReader {
private static boolean loggedUnknownFirework;
private static boolean loggedUnreadAbsolute;
private static final boolean AT_LEAST_1_9 = ServerVersion.isAtLeast("1.9");
private static final boolean AT_LEAST_1_14 = ServerVersion.isAtLeast("1.14");
private static final boolean AT_LEAST_26_3 = ServerVersion.isAtLeast("26.3");
public static final class Spawn {
public int id;
public UUID uuid;
public String typeName;
public int typeId = -1;
public double x;
public double y;
public double z;
public boolean positionKnown;
public Float yaw;
public Float pitch;
}
public static final class Relative {
public int id;
public boolean hasPosition;
public long dx;
public long dy;
public long dz;
public Float yaw;
public Float pitch;
}
public static final class Absolute {
public int id;
public boolean positionKnown;
public double x;
public double y;
public double z;
/** When set, that axis is a delta added to the last position the client was given. */
public boolean relativeX;
public boolean relativeY;
public boolean relativeZ;
public Float yaw;
public Float pitch;
}
public static final class Mount {
public int vehicleId;
public int[] passengers = new int[0];
}
public static final class Attach {
public int passengerId;
public int vehicleId;
public boolean mount;
}
private EntityPacketReader() {}
public static Spawn readSpawn(PacketContainer packet) {
Integer id = readInt(packet, 0);
if (id == null) {
return null;
}
Spawn spawn = new Spawn();
spawn.id = id.intValue();
spawn.uuid = readUuid(packet);
spawn.typeName = readTypeName(packet);
spawn.typeId = readTypeId(packet);
fillAbsolute(packet, spawn);
return spawn;
}
public static Relative readRelative(PacketContainer packet, boolean hasPosition, boolean hasLook) {
Integer id = readInt(packet, 0);
if (id == null) {
return null;
}
Relative relative = new Relative();
relative.id = id.intValue();
if (hasPosition) {
long[] delta = readDelta(packet);
if (delta != null) {
relative.hasPosition = true;
relative.dx = delta[0];
relative.dy = delta[1];
relative.dz = delta[2];
}
}
if (hasLook) {
readAngles(packet, relative);
}
return relative;
}
public static Absolute readAbsolute(PacketContainer packet) {
Integer id = readInt(packet, 0);
if (id == null) {
return null;
}
Absolute absolute = new Absolute();
absolute.id = id.intValue();
fillNestedPosition(packet, absolute);
if (!absolute.positionKnown) {
try {
StructureModifier<Double> doubles = packet.getDoubles();
Double x = doubles.readSafely(0);
Double y = doubles.readSafely(1);
Double z = doubles.readSafely(2);
if (x != null && y != null && z != null) {
absolute.positionKnown = true;
absolute.x = x.doubleValue();
absolute.y = y.doubleValue();
absolute.z = z.doubleValue();
}
}
catch (Throwable ignored) {}
}
if (!absolute.positionKnown) {
int[] legacy = legacyFixedPoint(packet);
if (legacy != null) {
absolute.positionKnown = true;
absolute.x = legacy[0] / 32.0D;
absolute.y = legacy[1] / 32.0D;
absolute.z = legacy[2] / 32.0D;
}
}
if (!absolute.positionKnown) {
noteUnreadAbsolute(packet);
}
readAngles(packet, absolute);
return absolute.positionKnown ? absolute : null;
}
/**
* 1.21.2+ teleport and position-sync store the coordinates inside a position/rotation
* holder (position vector, velocity vector, yaw, pitch), not as packet-level doubles.
* A 1.20.6 client receives that holder already resolved to an absolute teleport.
*/
private static void fillNestedPosition(PacketContainer packet, Absolute absolute) {
Object handle;
try {
handle = packet.getHandle();
}
catch (Throwable ignored) {
return;
}
if (handle == null) {
return;
}
Object holder = positionHolder(handle);
double[] xyz = holder == null ? null : firstVector(holder);
if (xyz == null) {
return;
}
absolute.positionKnown = true;
absolute.x = xyz[0];
absolute.y = xyz[1];
absolute.z = xyz[2];
float[] rotation = holder == null ? null : firstTwoFloats(holder);
if (rotation != null && absolute.yaw == null) {
absolute.yaw = Float.valueOf(rotation[0]);
absolute.pitch = Float.valueOf(rotation[1]);
}
markRelativeAxes(handle, absolute);
}
private static void noteUnreadAbsolute(PacketContainer packet) {
if (loggedUnreadAbsolute) {
return;
}
loggedUnreadAbsolute = true;
Object handle = null;
try {
handle = packet.getHandle();
}
catch (Throwable ignored) {}
System.out.println("entity position unread " + (handle == null ? "null" : handle.getClass().getName()));
}
/** The nested holder, or the packet itself when the vector sits directly on it. */
private static Object positionHolder(Object packet) {
if (firstVector(packet) != null) {
return packet;
}
Class<?> type = packet.getClass();
while (type != null && type != Object.class) {
Field[] fields = type.getDeclaredFields();
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (Modifier.isStatic(field.getModifiers()) || field.getType().isPrimitive()) {
continue;
}
Object value = readField(field, packet);
if (value != null && firstVector(value) != null) {
return value;
}
}
type = type.getSuperclass();
}
return null;
}
/** First vector on this object: its own three doubles, or the first nested vector field. */
private static double[] firstVector(Object object) {
if (object == null) {
return null;
}
double[] own = instanceDoubles(object);
if (own != null) {
return own;
}
Class<?> type = object.getClass();
while (type != null && type != Object.class) {
Field[] fields = type.getDeclaredFields();
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (Modifier.isStatic(field.getModifiers()) || field.getType().isPrimitive()) {
continue;
}
double[] nested = instanceDoubles(readField(field, object));
if (nested != null) {
return nested;
}
}
type = type.getSuperclass();
}
return null;
}
private static double[] instanceDoubles(Object object) {
if (object == null) {
return null;
}
Field[] fields = object.getClass().getDeclaredFields();
int count = 0;
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (!Modifier.isStatic(field.getModifiers()) && field.getType() == double.class) {
count++;
}
}
if (count < 3) {
return null;
}
double[] values = new double[3];
int found = 0;
for (int index = 0; index < fields.length && found < 3; index++) {
Field field = fields[index];
if (Modifier.isStatic(field.getModifiers()) || field.getType() != double.class) {
continue;
}
Object raw = readField(field, object);
if (!(raw instanceof Double)) {
return null;
}
values[found++] = ((Double) raw).doubleValue();
}
return found == 3 ? values : null;
}
private static float[] firstTwoFloats(Object object) {
Field[] fields = object.getClass().getDeclaredFields();
float[] values = null;
int found = 0;
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (Modifier.isStatic(field.getModifiers()) || field.getType() != float.class) {
continue;
}
Object raw = readField(field, object);
if (!(raw instanceof Float)) {
return null;
}
if (values == null) {
values = new float[2];
}
values[found++] = ((Float) raw).floatValue();
if (found == 2) {
return values;
}
}
return null;
}
/**
* Teleport packets name the axes that are deltas. Position sync has no such set,
* so every axis stays absolute. Names are the enum constants (X, Y, Z).
*/
private static void markRelativeAxes(Object packet, Absolute absolute) {
Class<?> type = packet.getClass();
while (type != null && type != Object.class) {
Field[] fields = type.getDeclaredFields();
for (int index = 0; index < fields.length; index++) {
Field field = fields[index];
if (Modifier.isStatic(field.getModifiers()) || !Set.class.isAssignableFrom(field.getType())) {
continue;
}
Object raw = readField(field, packet);
if (!(raw instanceof Set)) {
continue;
}
Set<?> flags = (Set<?>) raw;
java.util.Iterator<?> iterator = flags.iterator();
while (iterator.hasNext()) {
Object flag = iterator.next();
if (!(flag instanceof Enum)) {
continue;
}
String name = ((Enum<?>) flag).name();
if ("X".equals(name)) {
absolute.relativeX = true;
}
else if ("Y".equals(name)) {
absolute.relativeY = true;
}
else if ("Z".equals(name)) {
absolute.relativeZ = true;
}
}
return;
}
type = type.getSuperclass();
}
}
private static Object readField(Field field, Object target) {
try {
field.setAccessible(true);
return field.get(target);
}
catch (Throwable ignored) {
return null;
}
}
public static int[] readDestroy(PacketContainer packet) {
try {
int[] ids = packet.getIntegerArrays().readSafely(0);
if (ids != null) {
return ids;
}
}
catch (Throwable ignored) {}
try {
List<Integer> list = packet.getIntLists().readSafely(0);
if (list != null && !list.isEmpty()) {
int[] ids = new int[list.size()];
for (int index = 0; index < list.size(); index++) {
Integer id = list.get(index);
ids[index] = id == null ? -1 : id.intValue();
}
return ids;
}
}
catch (Throwable ignored) {}
Integer one = readInt(packet, 0);
return one == null ? new int[0] : new int[] { one.intValue() };
}
public static Mount readMount(PacketContainer packet) {
Integer vehicle = readInt(packet, 0);
if (vehicle == null) {
return null;
}
Mount mount = new Mount();
mount.vehicleId = vehicle.intValue();
mount.passengers = readPassengerIds(packet);
return mount;
}
public static Attach readAttach(PacketContainer packet) {
// 1.8: int 0 is 0 for mount and 1 for leash, then passenger, then vehicle.
Integer flag = readInt(packet, 0);
Integer passenger = readInt(packet, 1);
Integer vehicle = readInt(packet, 2);
if (flag == null || passenger == null || vehicle == null || flag.intValue() != 0) {
return null;
}
Attach attach = new Attach();
attach.mount = true;
attach.passengerId = passenger.intValue();
attach.vehicleId = vehicle.intValue();
return attach;
}
/**
* Entity id a firework rocket is attached to, from the metadata index vanilla uses.
* Null when that index is not in the packet. -1 when the optional is present but empty.
*/
public static Integer readFireworkAttached(PacketContainer packet) {
int index = fireworkAttachIndex();
try {
List<WrappedDataValue> values = packet.getDataValueCollectionModifier().readSafely(0);
Integer attached = entityIdAt(values, index);
if (attached != null || hasIndex(values, index)) {
return attached;
}
}
catch (Throwable ignored) {}
try {
List<WrappedWatchableObject> values = packet.getWatchableCollectionModifier().readSafely(0);
if (values == null) {
return null;
}
for (int cursor = 0; cursor < values.size(); cursor++) {
WrappedWatchableObject value = values.get(cursor);
if (value != null && value.getIndex() == index) {
return entityIdOf(value.getValue());
}
}
}
catch (Throwable ignored) {}
return null;
}
private static int fireworkAttachIndex() {
// Grim's offset from index 9: 1.12.2 and older use 7, through 1.16.5 use 8, newer use 9.
if (!ServerVersion.isAtLeast("1.13")) {
return 7;
}
if (!ServerVersion.isAtLeast("1.17")) {
return 8;
}
return 9;
}
private static boolean hasIndex(List<WrappedDataValue> values, int index) {
if (values == null) {
return false;
}
for (int cursor = 0; cursor < values.size(); cursor++) {
WrappedDataValue value = values.get(cursor);
if (value != null && value.getIndex() == index) {
return true;
}
}
return false;
}
private static Integer entityIdAt(List<WrappedDataValue> values, int index) {
if (values == null) {
return null;
}
for (int cursor = 0; cursor < values.size(); cursor++) {
WrappedDataValue value = values.get(cursor);
if (value != null && value.getIndex() == index) {
return entityIdOf(value.getValue());
}
}
return null;
}
private static Integer entityIdOf(Object value) {
if (value instanceof Integer) {
return (Integer) value;
}
// 1.14+ firework "attached to" is an unsigned optional int, not Optional<Integer>.
if (value instanceof OptionalInt) {
OptionalInt optional = (OptionalInt) value;
return optional.isPresent() ? Integer.valueOf(optional.getAsInt()) : Integer.valueOf(-1);
}
if (value instanceof Optional) {
Optional<?> optional = (Optional<?>) value;
if (!optional.isPresent()) {
return Integer.valueOf(-1);
}
Object inner = optional.get();
if (inner instanceof Integer) {
return (Integer) inner;
}
return entityIdOf(inner);
}
if (value != null && value.getClass().getName().endsWith("OptionalInt")) {
try {
Method present = value.getClass().getMethod("isPresent");
Object isPresent = present.invoke(value);
if (!(isPresent instanceof Boolean) || !((Boolean) isPresent).booleanValue()) {
return Integer.valueOf(-1);
}
Method read = value.getClass().getMethod("getAsInt");
Object id = read.invoke(value);
if (id instanceof Integer) {
return (Integer) id;
}
}
catch (Throwable ignored) {}
}
if (value != null && !loggedUnknownFirework) {
loggedUnknownFirework = true;
System.out.println("firework meta type " + value.getClass().getName() + " value=" + value);
}
return null;
}
public static Byte readSharedFlags(PacketContainer packet) {
try {
List<WrappedDataValue> values = packet.getDataValueCollectionModifier().readSafely(0);
Byte flags = flagsAtIndexZero(values);
if (flags != null) {
return flags;
}
}
catch (Throwable ignored) {}
try {
List<WrappedWatchableObject> values = packet.getWatchableCollectionModifier().readSafely(0);
if (values == null) {
return null;
}
for (int index = 0; index < values.size(); index++) {
WrappedWatchableObject value = values.get(index);
if (value != null && value.getIndex() == 0) {
Byte flags = asByte(value.getValue());
if (flags != null) {
return flags;
}
}
}
}
catch (Throwable ignored) {}
return null;
}
private static Byte flagsAtIndexZero(List<WrappedDataValue> values) {
if (values == null) {
return null;
}
for (int index = 0; index < values.size(); index++) {
WrappedDataValue value = values.get(index);
if (value != null && value.getIndex() == 0) {
Byte flags = asByte(value.getValue());
if (flags != null) {
return flags;
}
}
}
return null;
}
private static void fillAbsolute(PacketContainer packet, Spawn spawn) {
try {
StructureModifier<Double> doubles = packet.getDoubles();
Double x = doubles.readSafely(0);
Double y = doubles.readSafely(1);
Double z = doubles.readSafely(2);
if (x != null && y != null && z != null) {
spawn.positionKnown = true;
spawn.x = x.doubleValue();
spawn.y = y.doubleValue();
spawn.z = z.doubleValue();
}
}
catch (Throwable ignored) {}
if (!spawn.positionKnown) {
try {
BlockPosition block = packet.getBlockPositionModifier().readSafely(0);
if (block != null) {
spawn.positionKnown = true;
spawn.x = block.getX();
spawn.y = block.getY();
spawn.z = block.getZ();
}
}
catch (Throwable ignored) {}
}
if (!spawn.positionKnown) {
int[] legacy = legacyFixedPoint(packet);
if (legacy != null) {
spawn.positionKnown = true;
spawn.x = legacy[0] / 32.0D;
spawn.y = legacy[1] / 32.0D;
spawn.z = legacy[2] / 32.0D;
}
}
Float[] angles = readAnglePair(packet);
if (angles != null) {
spawn.yaw = angles[0];
spawn.pitch = angles[1];
}
}
private static int[] legacyFixedPoint(PacketContainer packet) {
try {
StructureModifier<Integer> integers = packet.getIntegers();
if (integers.size() >= 5) {
Integer x = integers.readSafely(2);
Integer y = integers.readSafely(3);
Integer z = integers.readSafely(4);
if (x != null && y != null && z != null) {
return new int[] { x.intValue(), y.intValue(), z.intValue() };
}
}
if (integers.size() >= 4) {
Integer x = integers.readSafely(1);
Integer y = integers.readSafely(2);
Integer z = integers.readSafely(3);
if (x != null && y != null && z != null) {
return new int[] { x.intValue(), y.intValue(), z.intValue() };
}
}
}
catch (Throwable ignored) {}
return null;
}
private static long[] readDelta(PacketContainer packet) {
if (AT_LEAST_26_3) {
long[] modern = ModernDelta.read(packet);
if (modern != null) {
return modern;
}
}
try {
if (AT_LEAST_1_14) {
StructureModifier<Short> shorts = packet.getShorts();
if (shorts.size() >= 3) {
return new long[] { shorts.read(0).shortValue(), shorts.read(1).shortValue(), shorts.read(2).shortValue() };
}
}
else if (AT_LEAST_1_9) {
StructureModifier<Integer> integers = packet.getIntegers();
if (integers.size() >= 4) {
return new long[] { integers.read(1).intValue(), integers.read(2).intValue(), integers.read(3).intValue() };
}
}
else {
StructureModifier<Byte> bytes = packet.getBytes();
if (bytes.size() >= 3) {
return new long[] { bytes.read(0).byteValue(), bytes.read(1).byteValue(), bytes.read(2).byteValue() };
}
}
}
catch (Throwable ignored) {}
if (!AT_LEAST_26_3) {
return ModernDelta.read(packet);
}
return null;
}
private static void readAngles(PacketContainer packet, Relative relative) {
Float[] angles = readAnglePair(packet);
if (angles != null) {
relative.yaw = angles[0];
relative.pitch = angles[1];
}
}
private static void readAngles(PacketContainer packet, Absolute absolute) {
Float[] angles = readAnglePair(packet);
if (angles != null) {
absolute.yaw = angles[0];
absolute.pitch = angles[1];
}
}
private static Float[] readAnglePair(PacketContainer packet) {
try {
StructureModifier<Float> floats = packet.getFloat();
if (floats.size() >= 2) {
Float yaw = floats.readSafely(0);
Float pitch = floats.readSafely(1);
if (yaw != null && pitch != null) {
return new Float[] { yaw, pitch };
}
}
}
catch (Throwable ignored) {}
try {
StructureModifier<Byte> bytes = packet.getBytes();
int size = bytes.size();
if (size >= 2) {
Byte yaw = bytes.readSafely(size - 2);
Byte pitch = bytes.readSafely(size - 1);
if (yaw != null && pitch != null) {
return new Float[] { Float.valueOf(angle(yaw.byteValue())), Float.valueOf(angle(pitch.byteValue())) };
}
}
}
catch (Throwable ignored) {}
return null;
}
private static float angle(byte value) {
return value * 360.0f / 256.0f;
}
private static int[] readPassengerIds(PacketContainer packet) {
try {
int[] ids = packet.getIntegerArrays().readSafely(0);
if (ids != null) {
return ids;
}
}
catch (Throwable ignored) {}
try {
List<Integer> list = packet.getIntLists().readSafely(0);
if (list != null) {
int[] ids = new int[list.size()];
for (int index = 0; index < list.size(); index++) {
Integer id = list.get(index);
ids[index] = id == null ? -1 : id.intValue();
}
return ids;
}
}
catch (Throwable ignored) {}
return new int[0];
}
private static Integer readInt(PacketContainer packet, int index) {
try {
return packet.getIntegers().readSafely(index);
}
catch (Throwable ignored) {
return null;
}
}
private static UUID readUuid(PacketContainer packet) {
try {
return packet.getUUIDs().readSafely(0);
}
catch (Throwable ignored) {
return null;
}
}
private static String readTypeName(PacketContainer packet) {
try {
EntityType type = packet.getEntityTypeModifier().readSafely(0);
return type == null ? null : type.name();
}
catch (Throwable ignored) {
return null;
}
}
private static int readTypeId(PacketContainer packet) {
try {
if (packet.getDoubles().size() < 3) {
return -1;
}
Integer type = packet.getIntegers().readSafely(1);
return type == null ? -1 : type.intValue();
}
catch (Throwable ignored) {
return -1;
}
}
private static Byte asByte(Object value) {
return value instanceof Byte ? (Byte) value : null;
}
/**
* 26.3 encodes a relative move as VecDelta instead of three shorts.
* Absent on older servers, in which case the reader falls through.
*/
private static final class ModernDelta {
private static final Class<?> DELTA;
private static final Class<?> LINEAR;
private static final Class<?> STEPPED;
private static final Method STEPS;
static {
Class<?> delta = null;
Class<?> linear = null;
Class<?> stepped = null;
Method steps = null;
try {
delta = Class.forName("net.minecraft.network.protocol.game.VecDelta");
linear = Class.forName("net.minecraft.network.protocol.game.VecDelta$Linear");
stepped = Class.forName("net.minecraft.network.protocol.game.VecDelta$Stepped");
steps = stepped.getMethod("steps");
}
catch (Throwable ignored) {}
DELTA = delta;
LINEAR = linear;
STEPPED = stepped;
STEPS = steps;
}
static long[] read(PacketContainer packet) {
if (DELTA == null) {
return null;
}
try {
Object delta = packet.getModifier().withType(DELTA).readSafely(0);
if (delta == null) {
return null;
}
if (LINEAR.isInstance(delta)) {
return axes(delta);
}
if (STEPPED.isInstance(delta) && STEPS != null) {
long dx = 0L;
long dy = 0L;
long dz = 0L;
Object raw = STEPS.invoke(delta);
if (raw instanceof List) {
List<?> list = (List<?>) raw;
for (int index = 0; index < list.size(); index++) {
long[] step = axes(list.get(index));
if (step == null) {
return null;
}
dx += step[0];
dy += step[1];
dz += step[2];
}
}
return new long[] { dx, dy, dz };
}
}
catch (Throwable ignored) {}
return null;
}
private static long[] axes(Object value) {
if (value == null) {
return null;
}
StructureModifier<Short> shorts = new StructureModifier<Short>(value.getClass()).withTarget(value).withType(short.class);
if (shorts.size() < 3) {
return null;
}
return new long[] { shorts.read(0).shortValue(), shorts.read(1).shortValue(), shorts.read(2).shortValue() };
}
}
}
@@ -0,0 +1,260 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.net.protocollib;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketPostListener;
import fr.neatmonster.nocheatplus.checks.net.entity.ClientEntity;
import fr.neatmonster.nocheatplus.checks.net.entity.ClientEntityTracker;
import fr.neatmonster.nocheatplus.checks.net.entity.EntityPacketReader;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData;
/**
* Keeps {@link ClientEntityTracker} in step with the packets each client actually receives.
* The transaction clock is {@link fr.neatmonster.nocheatplus.checks.net.protocollib.TransactionAdapter}.
*/
public class EntityActivityAdapter extends BaseAdapter {
private static PacketType[] listenTypes() {
List<PacketType> types = new ArrayList<PacketType>();
add(types, PacketType.Play.Server.SPAWN_ENTITY);
add(types, PacketType.Play.Server.SPAWN_ENTITY_LIVING);
add(types, PacketType.Play.Server.NAMED_ENTITY_SPAWN);
add(types, PacketType.Play.Server.SPAWN_ENTITY_PAINTING);
add(types, PacketType.Play.Server.ENTITY_TELEPORT);
add(types, PacketType.Play.Server.ENTITY_POSITION_SYNC);
add(types, PacketType.Play.Server.REL_ENTITY_MOVE);
add(types, PacketType.Play.Server.REL_ENTITY_MOVE_LOOK);
add(types, PacketType.Play.Server.ENTITY_LOOK);
add(types, PacketType.Play.Server.ENTITY_DESTROY);
add(types, PacketType.Play.Server.ENTITY_METADATA);
add(types, PacketType.Play.Server.ENTITY_STATUS);
add(types, PacketType.Play.Server.MOUNT);
add(types, PacketType.Play.Server.ATTACH_ENTITY);
// 1.20.6 and older servers do not have this packet. The server tick steps entities there.
// It appears only when the server is 1.21.2 or newer, once per server tick.
if (ServerVersion.isAtLeast("1.21.2")) {
add(types, PacketType.Play.Client.CLIENT_TICK_END);
}
return types.toArray(new PacketType[types.size()]);
}
private static void add(List<PacketType> types, PacketType type) {
try {
if (type != null && type.isSupported()) {
types.add(type);
}
}
catch (Throwable ignored) {}
}
/** True when this client actually sends tick-end. Older clients only see ViaVersion's fake copy, and only if the server protocol has the packet. */
private static boolean sendsRealTickEnd(Player player) {
if (!ServerVersion.isAtLeast("1.21.2")) {
return false;
}
IPlayerData data = DataManager.getPlayerDataSafe(player);
if (data == null) {
return false;
}
ClientVersion version = data.getClientVersion();
return version != null && version.isAtLeast(ClientVersion.V_1_21_2);
}
private int failures;
public EntityActivityAdapter(Plugin plugin) {
super(plugin, ListenerPriority.LOW, listenTypes());
}
@Override
public void onPacketReceiving(PacketEvent event) {
if (!accept(event)) {
return;
}
try {
Player player = event.getPlayer();
ClientEntityTracker tracker = tracker(player);
if (tracker == null) {
return;
}
PacketType type = event.getPacketType();
if (type == PacketType.Play.Client.CLIENT_TICK_END) {
// Registered only when the server is 1.21.2 or newer. ViaVersion still inserts a
// copy for older clients. That copy is not a client tick, so only a real 1.21.2+
// client steps here. Everyone else steps on the server tick.
if (sendsRealTickEnd(player)) {
tracker.onTickEnd();
}
}
}
catch (Throwable throwable) {
fail(throwable);
}
}
@Override
public void onPacketSending(PacketEvent event) {
if (event.isCancelled() || !accept(event)) {
return;
}
try {
Player player = event.getPlayer();
ClientEntityTracker tracker = tracker(player);
if (tracker == null) {
return;
}
PacketType type = event.getPacketType();
PacketContainer packet = event.getPacket();
if (type == PacketType.Play.Server.SPAWN_ENTITY || type == PacketType.Play.Server.SPAWN_ENTITY_LIVING
|| type == PacketType.Play.Server.NAMED_ENTITY_SPAWN || type == PacketType.Play.Server.SPAWN_ENTITY_PAINTING) {
EntityPacketReader.Spawn spawn = EntityPacketReader.readSpawn(packet);
if (spawn != null) {
tracker.onSpawn(player, spawn.id, spawn.uuid, kind(type, spawn.typeName), spawn.typeName, spawn.typeId,
spawn.x, spawn.y, spawn.z, spawn.positionKnown, spawn.yaw, spawn.pitch);
bracketAfter(event, player, tracker);
}
}
else if (type == PacketType.Play.Server.REL_ENTITY_MOVE || type == PacketType.Play.Server.REL_ENTITY_MOVE_LOOK
|| type == PacketType.Play.Server.ENTITY_LOOK) {
boolean look = type != PacketType.Play.Server.REL_ENTITY_MOVE;
boolean move = type != PacketType.Play.Server.ENTITY_LOOK;
EntityPacketReader.Relative relative = EntityPacketReader.readRelative(packet, move, look);
if (relative != null) {
tracker.onRelativeMove(player, relative.id, relative.dx, relative.dy, relative.dz, relative.hasPosition, relative.yaw, relative.pitch);
}
}
else if (type == PacketType.Play.Server.ENTITY_TELEPORT || type == PacketType.Play.Server.ENTITY_POSITION_SYNC) {
EntityPacketReader.Absolute absolute = EntityPacketReader.readAbsolute(packet);
if (absolute != null) {
tracker.onAbsoluteMove(player, absolute.id, absolute.x, absolute.y, absolute.z,
absolute.relativeX, absolute.relativeY, absolute.relativeZ, absolute.yaw, absolute.pitch,
type == PacketType.Play.Server.ENTITY_TELEPORT);
}
}
else if (type == PacketType.Play.Server.ENTITY_DESTROY) {
tracker.onDestroy(player, EntityPacketReader.readDestroy(packet));
}
else if (type == PacketType.Play.Server.ENTITY_METADATA) {
Integer id = packet.getIntegers().readSafely(0);
if (id != null) {
tracker.onMetadata(player, id.intValue(), EntityPacketReader.readSharedFlags(packet),
EntityPacketReader.readFireworkAttached(packet));
bracketAfter(event, player, tracker);
}
}
else if (type == PacketType.Play.Server.ENTITY_STATUS) {
Integer id = packet.getIntegers().readSafely(0);
Byte status = packet.getBytes().readSafely(0);
if (id != null && status != null) {
tracker.onStatus(player, id.intValue(), status.byteValue());
}
}
else if (type == PacketType.Play.Server.MOUNT) {
EntityPacketReader.Mount mount = EntityPacketReader.readMount(packet);
if (mount != null) {
tracker.onMount(player, mount.vehicleId, mount.passengers);
}
}
else if (type == PacketType.Play.Server.ATTACH_ENTITY && !ServerVersion.isAtLeast("1.9")) {
EntityPacketReader.Attach attach = EntityPacketReader.readAttach(packet);
if (attach != null) {
tracker.onLegacyAttach(player, attach.passengerId, attach.vehicleId);
}
}
}
catch (Throwable throwable) {
fail(throwable);
}
}
private void bracketAfter(PacketEvent event, final Player player, final ClientEntityTracker tracker) {
if (!tracker.consumeBracketPing()) {
return;
}
final Plugin owner = getPlugin();
try {
event.getNetworkMarker().addPostListener(new PacketPostListener() {
@Override
public Plugin getPlugin() {
return owner;
}
@Override
public void onPostEvent(PacketEvent sent) {
tracker.bracketPing(player);
}
});
}
catch (Throwable ignored) {
tracker.bracketPing(player);
}
}
private static ClientEntity.Kind kind(PacketType type, String typeName) {
if (type == PacketType.Play.Server.NAMED_ENTITY_SPAWN || "PLAYER".equals(typeName)) {
return ClientEntity.Kind.PLAYER;
}
if (type == PacketType.Play.Server.SPAWN_ENTITY_LIVING) {
return ClientEntity.Kind.LIVING;
}
if (type == PacketType.Play.Server.SPAWN_ENTITY_PAINTING) {
return ClientEntity.Kind.PAINTING;
}
return ClientEntity.Kind.OBJECT;
}
private static ClientEntityTracker tracker(Player player) {
if (player == null) {
return null;
}
IPlayerData data = DataManager.getPlayerDataSafe(player);
if (data == null) {
return null;
}
return data.getGenericInstance(ClientEntityTracker.class);
}
private static boolean accept(PacketEvent event) {
try {
if (event.isPlayerTemporary()) {
return false;
}
}
catch (NoSuchMethodError ignored) {}
return event.getPlayer() != null;
}
private void fail(Throwable throwable) {
if (failures++ < 3) {
StaticLog.logWarning("Entity tracker packet listener failed: " + throwable.getMessage());
}
}
}
@@ -0,0 +1,84 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.net.protocollib;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.AutoWrapper;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.checks.net.model.DataPacketInput;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData;
/**
* Listen to the Steer_vehicle packet for extrapolating input information sent by the player (1.21.2+)
* The packet is actually named "Player_Input" in the Minecraft protocol, but ProtocolLib uses the former name for compatibility.
*/
public class InputsAdapter extends BaseAdapter {
private static AutoWrapper<DataPacketInput> INPUT_WRAPPER;
private static PacketType[] initPacketTypes() {
final List<PacketType> types = new LinkedList<PacketType>(Arrays.asList(PacketType.Play.Client.STEER_VEHICLE));
return types.toArray(new PacketType[types.size()]);
}
public InputsAdapter(Plugin plugin) {
super(plugin, ListenerPriority.MONITOR, initPacketTypes());
}
@Override
public void onPacketReceiving(final PacketEvent event) {
handleInputPacket(event);
}
private void handleInputPacket(PacketEvent event) {
try {
if (event.isPlayerTemporary()) return;
}
catch (NoSuchMethodError e) {
if (event.getPlayer() == null) {
counters.add(ProtocolLibComponent.idNullPlayer, 1);
return;
}
if (DataManager.getPlayerDataSafe(event.getPlayer()) == null) {
return;
}
}
final Player player = event.getPlayer();
final PacketContainer packet = event.getPacket();
final IPlayerData pData = DataManager.getPlayerData(player);
final NetData data = pData.getGenericInstance(NetData.class);
// Instead of a primitive, we're being handed the full Input object from Mojang’s code.
StructureModifier<Object> objs = packet.getModifier().withType(Object.class);
Object raw = objs.read(0);
if (INPUT_WRAPPER == null) {
INPUT_WRAPPER = AutoWrapper.wrap(DataPacketInput.class, raw.getClass());
}
final DataPacketInput in = INPUT_WRAPPER.wrap(raw);
data.addInputQueue(in);
}
}
@@ -44,13 +44,11 @@ public class KeepAliveAdapter extends BaseAdapter {
private final KeepAliveFrequency frequencyCheck = new KeepAliveFrequency(); private final KeepAliveFrequency frequencyCheck = new KeepAliveFrequency();
public KeepAliveAdapter(Plugin plugin) { public KeepAliveAdapter(Plugin plugin) {
super(plugin, ListenerPriority.LOW, PacketType.Play.Client.KEEP_ALIVE); super(plugin, ListenerPriority.LOW, PacketType.Play.Client.KEEP_ALIVE, PacketType.Play.Server.KEEP_ALIVE);
this.checkType = CheckType.NET_KEEPALIVEFREQUENCY; this.checkType = CheckType.NET_KEEPALIVEFREQUENCY;
// Add feature tags for checks. // Add feature tags for checks.
if (NCPAPIProvider.getNoCheatPlusAPI().getWorldDataManager().isActiveAnywhere( if (NCPAPIProvider.getNoCheatPlusAPI().getWorldDataManager().isActiveAnywhere(CheckType.NET_KEEPALIVEFREQUENCY)) {
CheckType.NET_KEEPALIVEFREQUENCY)) { NCPAPIProvider.getNoCheatPlusAPI().addFeatureTags("checks", Arrays.asList(KeepAliveFrequency.class.getSimpleName()));
NCPAPIProvider.getNoCheatPlusAPI().addFeatureTags(
"checks", Arrays.asList(KeepAliveFrequency.class.getSimpleName()));
} }
NCPAPIProvider.getNoCheatPlusAPI().addComponent(frequencyCheck); NCPAPIProvider.getNoCheatPlusAPI().addComponent(frequencyCheck);
} }
@@ -72,11 +70,12 @@ public class KeepAliveAdapter extends BaseAdapter {
final IPlayerData pData = DataManager.getPlayerDataSafe(player); final IPlayerData pData = DataManager.getPlayerDataSafe(player);
if (pData == null) return; if (pData == null) return;
final NetData data = pData.getGenericInstance(NetData.class); final NetData data = pData.getGenericInstance(NetData.class);
recordKeepAlivePacketDetails(event, data, time);
data.lastKeepAliveTime = time; data.lastKeepAliveTime = time;
final NetConfig cc = pData.getGenericInstance(NetConfig.class); final NetConfig cc = pData.getGenericInstance(NetConfig.class);
// Run check(s). // Run check(s).
// TODO: Match vs. outgoing keep alive requests. // KeepAlive model: outgoing server ids are recorded in onPacketSending; matching replies are expected.
// TODO: Better modeling of actual packet sequences (flying vs. keep alive vs. request/ping). // TODO: Better modeling of actual packet sequences (flying vs. keep alive vs. request/ping).
// TODO: Better integration with god-mode check / trigger reset ndt. // TODO: Better integration with god-mode check / trigger reset ndt.
if (frequencyCheck.isEnabled(player, pData) if (frequencyCheck.isEnabled(player, pData)
@@ -85,9 +84,25 @@ public class KeepAliveAdapter extends BaseAdapter {
} }
} }
@Override private void recordKeepAlivePacketDetails(final PacketEvent event, final NetData data, final long time) {
public void onPacketSending(PacketEvent event) { final KeepAlivePacketInfo info = KeepAlivePacketInfo.read(event.getPacket());
// TODO: Maybe detect if keep alive wasn't asked for + allow cancel. data.recordKeepAlivePacket(time, info.idAvailable, info.id, info.idType, info.longCount, info.intCount,
event.isAsync(), Thread.currentThread().getName());
} }
@Override
public void onPacketSending(PacketEvent event) {
final Player player = event.getPlayer();
if (player == null) {
return;
}
final IPlayerData pData = DataManager.getPlayerDataSafe(player);
if (pData == null) {
return;
}
final KeepAlivePacketInfo info = KeepAlivePacketInfo.read(event.getPacket());
pData.getGenericInstance(NetData.class).recordOutgoingKeepAlivePacket(System.currentTimeMillis(),
info.idAvailable, info.id, info.idType, info.longCount, info.intCount,
event.isAsync(), Thread.currentThread().getName());
}
} }
@@ -0,0 +1,74 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.net.protocollib;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
/**
* ProtocolLib packet-shape extraction for keepalive request/response ids.
*/
final class KeepAlivePacketInfo {
final boolean idAvailable;
final long id;
final String idType;
final int longCount;
final int intCount;
private KeepAlivePacketInfo(final boolean idAvailable, final long id, final String idType,
final int longCount, final int intCount) {
this.idAvailable = idAvailable;
this.id = id;
this.idType = idType;
this.longCount = longCount;
this.intCount = intCount;
}
static KeepAlivePacketInfo read(final PacketContainer packet) {
boolean idAvailable = false;
long id = 0L;
String idType = "none";
int longCount = -1;
int intCount = -1;
try {
final StructureModifier<Long> longs = packet.getLongs();
longCount = longs.size();
if (longCount > 0) {
final Long value = longs.read(0);
if (value != null) {
idAvailable = true;
id = value.longValue();
idType = "long";
}
}
}
catch (Throwable ignored) {}
try {
final StructureModifier<Integer> integers = packet.getIntegers();
intCount = integers.size();
if (!idAvailable && intCount > 0) {
final Integer value = integers.read(0);
if (value != null) {
idAvailable = true;
id = value.longValue();
idType = "int";
}
}
}
catch (Throwable ignored) {}
return new KeepAlivePacketInfo(idAvailable, id, idType, longCount, intCount);
}
}
@@ -34,9 +34,11 @@ import com.comphenix.protocol.reflect.StructureModifier;
import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.net.FlyingFrequency; import fr.neatmonster.nocheatplus.checks.net.FlyingFrequency;
import fr.neatmonster.nocheatplus.checks.net.Moving; import fr.neatmonster.nocheatplus.checks.net.Moving;
import fr.neatmonster.nocheatplus.checks.net.NetConfig; import fr.neatmonster.nocheatplus.checks.net.NetConfig;
import fr.neatmonster.nocheatplus.checks.net.entity.ClientEntityTracker;
import fr.neatmonster.nocheatplus.checks.net.NetData; import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.checks.net.WrongTurn; import fr.neatmonster.nocheatplus.checks.net.WrongTurn;
import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying; import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying;
@@ -51,6 +53,7 @@ import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.time.monotonic.Monotonic; import fr.neatmonster.nocheatplus.time.monotonic.Monotonic;
import fr.neatmonster.nocheatplus.utilities.CheckUtils; import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.StringUtil; import fr.neatmonster.nocheatplus.utilities.StringUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency; import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil; import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
@@ -62,7 +65,8 @@ import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
* *
*/ */
public class MovingFlying extends BaseAdapter { public class MovingFlying extends BaseAdapter {
private static final boolean isServerAtLeast1_21_3 = ServerVersion.compareMinecraftVersion("1.21.3") >= 0;
private static final boolean isServerAtLeast1_21_3 = ServerVersion.isAtLeast("1.21.3");
// Setup for flying packets. // Setup for flying packets.
public static final int indexOnGround = 0; public static final int indexOnGround = 0;
public static final int indexhorizontalCollision = isServerAtLeast1_21_3 ? 1 : 0; public static final int indexhorizontalCollision = isServerAtLeast1_21_3 ? 1 : 0;
@@ -71,26 +75,34 @@ public class MovingFlying extends BaseAdapter {
public static final int indexX = 0; public static final int indexX = 0;
public static final int indexY = 1; public static final int indexY = 1;
public static final int indexZ = 2; public static final int indexZ = 2;
/** 1.7.10 */ public static final int indexStance = 3; // 1.7.10
public static final int indexStance = 3;
public static final int indexYaw = 0; public static final int indexYaw = 0;
public static final int indexPitch = 1; public static final int indexPitch = 1;
// Setup for teleport accept packet.
private static PacketType confirmTeleportType;
private static boolean acceptConfirmTeleportPackets;
private final Plugin plugin = Bukkit.getPluginManager().getPlugin("NoCheatPlus"); private final Plugin plugin = Bukkit.getPluginManager().getPlugin("NoCheatPlus");
private static PacketType[] initPacketTypes() { private static PacketType[] initPacketTypes() {
final List<PacketType> types = new LinkedList<PacketType>(Arrays.asList(PacketType.Play.Client.LOOK, PacketType.Play.Client.POSITION, PacketType.Play.Client.POSITION_LOOK)); final List<PacketType> types = new LinkedList<PacketType>(Arrays.asList(PacketType.Play.Client.LOOK, PacketType.Play.Client.POSITION, PacketType.Play.Client.POSITION_LOOK));
if (ServerVersion.compareMinecraftVersion("1.17") < 0) { if (ServerVersion.isLowerThan("1.17")) {
types.add(PacketType.Play.Client.FLYING); types.add(PacketType.Play.Client.FLYING);
StaticLog.logInfo("Add listener for legacy PlayInFlying packet."); StaticLog.logInfo("Add listener for legacy PlayInFlying packet.");
} }
else types.add(PacketType.Play.Client.GROUND); else types.add(PacketType.Play.Client.GROUND);
// Add confirm teleport. // Add confirm teleport.
// PacketPlayInTeleportAccept // PacketPlayInTeleportAccept
PacketType confirmType = ProtocolLibComponent.findPacketTypeByName(Protocol.PLAY, Sender.CLIENT, "TeleportAccept"); confirmTeleportType = ProtocolLibComponent.findPacketTypeByName(Protocol.PLAY, Sender.CLIENT, "AcceptTeleportation");
if (confirmType != null && ServerVersion.compareMinecraftVersion("1.9") >= 0) { if (confirmTeleportType == null) { // Fallback check for the old packet name.
StaticLog.logInfo("Confirm teleport packet available (via name): " + confirmType); confirmTeleportType = ProtocolLibComponent.findPacketTypeByName(Protocol.PLAY, Sender.CLIENT, "TeleportAccept");
types.add(confirmType);
} }
if (confirmTeleportType != null && ServerVersion.isAtLeast("1.9")) {
StaticLog.logInfo("Confirm teleport packet available (via name): " + confirmTeleportType);
types.add(confirmTeleportType);
acceptConfirmTeleportPackets = true;
}
else acceptConfirmTeleportPackets = false;
return types.toArray(new PacketType[types.size()]); return types.toArray(new PacketType[types.size()]);
} }
@@ -102,7 +114,6 @@ public class MovingFlying extends BaseAdapter {
private final WrongTurn wrongTurn = new WrongTurn(); private final WrongTurn wrongTurn = new WrongTurn();
private final int idFlying = counters.registerKey("packet.flying"); private final int idFlying = counters.registerKey("packet.flying");
private final int idAsyncFlying = counters.registerKey("packet.flying.asynchronous"); private final int idAsyncFlying = counters.registerKey("packet.flying.asynchronous");
/** If a packet can't be parsed, this time stamp is set for occasional logging. */ /** If a packet can't be parsed, this time stamp is set for occasional logging. */
@@ -112,10 +123,6 @@ public class MovingFlying extends BaseAdapter {
private final HashSet<PACKET_CONTENT> validContent = new LinkedHashSet<PACKET_CONTENT>(); private final HashSet<PACKET_CONTENT> validContent = new LinkedHashSet<PACKET_CONTENT>();
private final PacketType confirmTeleportType = ProtocolLibComponent.findPacketTypeByName(Protocol.PLAY, Sender.CLIENT, "TeleportAccept");
private boolean acceptConfirmTeleportPackets = confirmTeleportType != null;
public MovingFlying(Plugin plugin) { public MovingFlying(Plugin plugin) {
// PacketPlayInFlying[3, legacy: 10] // PacketPlayInFlying[3, legacy: 10]
super(plugin, ListenerPriority.LOW, initPacketTypes()); super(plugin, ListenerPriority.LOW, initPacketTypes());
@@ -219,28 +226,28 @@ public class MovingFlying extends BaseAdapter {
// Interpret the packet content. // Interpret the packet content.
final DataPacketFlying packetData = interpretPacket(event, time); final DataPacketFlying packetData = interpretPacket(event, time);
// Early return tests, if the packet can be interpreted. // Early return tests, if the packet can be interpreted.
boolean skipFlyingFrequency = false; boolean skipChecks = false;
if (packetData != null) { if (packetData != null) {
// Prevent processing packets with obviously malicious content. // Prevent processing packets with obviously malicious content.
if (isInvalidContent(packetData)) { if (isInvalidContent(packetData)) {
// TODO: extra actions: log and kick (cancel state is not evaluated)
event.setCancelled(true); event.setCancelled(true);
// Do request and improbable update here for good measure.
TickTask.requestImprobableUpdate(player.getUniqueId(), 1.0f);
if (pData.isDebugActive(this.checkType)) { if (pData.isDebugActive(this.checkType)) {
debug(player, "Incoming packet, cancel due to malicious content: " + packetData.toString()); debug(player, "Incoming flying packet, cancel due to malicious content: " + packetData.toString());
} }
return; return;
} }
switch(data.teleportQueue.processAck(packetData)) { switch (data.teleportQueue.processAck(packetData)) {
case WAITING: { case WAITING: {
if (pData.isDebugActive(this.checkType)) { if (pData.isDebugActive(this.checkType)) {
debug(player, "Incoming packet, still waiting for ACK on outgoing position."); debug(player, "Incoming flying packet, still waiting for ACK on outgoing position.");
} }
if (confirmTeleportType != null && cc.supersededFlyingCancelWaiting) { if (confirmTeleportType != null && cc.supersededFlyingCancelWaiting) {
// Don't add to the flying queue for now (assumed invalid). // Don't add to the flying queue for now (assumed invalid).
final AckReference ackReference = data.teleportQueue.getLastAckReference(); final AckReference ackReference = data.teleportQueue.getLastAckReference();
if (ackReference.lastOutgoingId != Integer.MIN_VALUE if (ackReference.lastOutgoingId != Integer.MIN_VALUE && ackReference.lastOutgoingId != ackReference.maxConfirmedId) {
&& ackReference.lastOutgoingId != ackReference.maxConfirmedId) {
// Still waiting for a 'confirm teleport' packet. More or less safe to cancel this out. // Still waiting for a 'confirm teleport' packet. More or less safe to cancel this out.
/* /*
* TODO: The actual issue with this, apart from * TODO: The actual issue with this, apart from
@@ -255,18 +262,17 @@ public class MovingFlying extends BaseAdapter {
* position-correction teleporting the server does, for * position-correction teleporting the server does, for
* the cases a plugin can handle. * the cases a plugin can handle.
*/ */
// TODO: Timeout -> either skip cancel or schedule a set back (to last valid pos or other). // See todo below on if (cancel) {...}
// TODO: Config? cancel = true;
cancel = true;
} }
} }
break; break;
} }
case ACK: { case ACK: {
// Skip processing ACK packets, no cancel. // Skip processing ACK packets, no cancel.
skipFlyingFrequency = true; skipChecks = true;
if (pData.isDebugActive(this.checkType)) { if (pData.isDebugActive(this.checkType)) {
debug(player, "Incoming packet, interpret as ACK for outgoing position."); debug(player, "Incoming flying packet, interpret as ACK for outgoing position. Skip checks.");
} }
} }
default: { default: {
@@ -276,36 +282,53 @@ public class MovingFlying extends BaseAdapter {
} }
// Add as valid packet (exclude invalid coordinates etc. for now). // Add as valid packet (exclude invalid coordinates etc. for now).
validContent.add(packetData.getSimplifiedContentType()); validContent.add(packetData.getSimplifiedContentType());
if (packetData.hasPos && !skipChecks) {
ClientEntityTracker pushTracker = pData.getGenericInstance(ClientEntityTracker.class);
if (pushTracker != null) {
pushTracker.notePositionPacket(packetData.getX(), packetData.getY(), packetData.getZ());
}
}
} }
// Actual packet frequency check. // Actual packet frequency check.
// TODO: Consider using the NetStatic check. // TODO: Consider using the NetStatic check.
if (!cancel && !skipFlyingFrequency if (!cancel && !skipChecks
&& flyingFrequency.check(player, packetData, time, data, cc, pData) && flyingFrequency.check(player, packetData, time, data, cc, pData)
&& !pData.hasBypass(CheckType.NET_FLYINGFREQUENCY, player)) { && !pData.hasBypass(CheckType.NET_FLYINGFREQUENCY, player)) {
cancel = true; cancel = true;
// Also request a set back here
data.requestSetBack(player, this, plugin, CheckType.NET_FLYINGFREQUENCY);
} }
// More packet checks. // More packet checks.
if (!cancel && !skipFlyingFrequency && pData.isCheckActive(CheckType.NET_MOVING, player) if (!cancel && !skipChecks && pData.isCheckActive(CheckType.NET_MOVING, player)
&& moving.check(player, packetData, data, cc, pData, plugin) && moving.check(player, packetData, data, cc, pData, plugin)
&& !pData.hasBypass(CheckType.NET_MOVING, player)) { && !pData.hasBypass(CheckType.NET_MOVING, player)) {
cancel = true; cancel = true;
// Also request a set back here
data.requestSetBack(player, this, plugin, CheckType.NET_MOVING);
} }
// Illegal pitch check // Illegal pitch check
if (!cancel && pData.isCheckActive(CheckType.NET_WRONGTURN, player) if (!cancel && pData.isCheckActive(CheckType.NET_WRONGTURN, player)
&& wrongTurn.check(player, packetData.getPitch(), data, cc) && wrongTurn.check(player, packetData.getPitch(), data, cc)
&& !pData.hasBypass(CheckType.NET_WRONGTURN, player)) { && !pData.hasBypass(CheckType.NET_WRONGTURN, player)) {
cancel = true; // Is it a good idea to cancel or should we just reset the players pitch? cancel = true; // Is it a good idea to cancel or should we just reset the players pitch?
} }
if (!cancel && pData.isCheckActive(CheckType.MOVING_NOFALL, player)) {
final MovingData mData = pData.getGenericInstance(MovingData.class);
final float noFallFallDistance = mData.noFallFallDistance;
if (mData.requestTrueForGround) {
// If damage has already dealt
if (noFallFallDistance > 0.0) {
player.setFallDistance(noFallFallDistance);
event.getPacket().getBooleans().write(indexOnGround, mData.requestTrueForGround);
}
mData.requestTrueForGround = false;
}
}
// Process cancel and debug log. // Process cancel and debug log.
if (cancel) { if (cancel) {
/*
* TODO: Test if this can fix packet fly exploits; it will also cause set backs on flying packets awaiting ACK(s)
* Previously, NCP would simply cancel these flying packets out, without really restricting movement.
*/
data.requestSetBack(player, this, plugin, CheckType.NET);
event.setCancelled(true); event.setCancelled(true);
} }
@@ -314,8 +337,14 @@ public class MovingFlying extends BaseAdapter {
} }
} }
/**
* Checks if the given {@link DataPacketFlying} contains invalid position or look data. <br>
* Delegates to {@link LocUtil#isBadCoordinate(double...)} and {@link LocUtil#isBadCoordinate(float...)}.<br>
* If this returns true, invalid packets won't be added to the flying queue, and will be cancelled immediately.
*
* @param packetData the flying packet data to validate
* @return true if the packet contains invalid position or look data, false otherwise
*/
private boolean isInvalidContent(final DataPacketFlying packetData) { private boolean isInvalidContent(final DataPacketFlying packetData) {
if (packetData.hasPos && LocUtil.isBadCoordinate(packetData.getX(), packetData.getY(), packetData.getZ())) { if (packetData.hasPos && LocUtil.isBadCoordinate(packetData.getX(), packetData.getY(), packetData.getZ())) {
return true; return true;
@@ -328,16 +357,11 @@ public class MovingFlying extends BaseAdapter {
/** /**
* Interpret the packet content and do with it whatever is suitable. * Interpret the packet content and do with it whatever is suitable.
* @param player
* @param event * @param event
* @param allScore
* @param time * @param time
* @param data
* @param cc
* @return Packet data if successful, or null on packet mismatch. * @return Packet data if successful, or null on packet mismatch.
*/ */
private DataPacketFlying interpretPacket(final PacketEvent event, final long time) { private DataPacketFlying interpretPacket(final PacketEvent event, final long time) {
final PacketContainer packet = event.getPacket(); final PacketContainer packet = event.getPacket();
final List<Boolean> booleans = packet.getBooleans().getValues(); final List<Boolean> booleans = packet.getBooleans().getValues();
if (booleans.size() != (isServerAtLeast1_21_3 ? 4 : 3)) { if (booleans.size() != (isServerAtLeast1_21_3 ? 4 : 3)) {
@@ -345,7 +369,7 @@ public class MovingFlying extends BaseAdapter {
return null; return null;
} }
final boolean onGround = booleans.get(MovingFlying.indexOnGround).booleanValue(); final boolean onGround = booleans.get(MovingFlying.indexOnGround).booleanValue();
final boolean horizontalCollision = isServerAtLeast1_21_3 ? booleans.get(MovingFlying.indexhorizontalCollision).booleanValue() : false; final boolean horizontalCollision = isServerAtLeast1_21_3 && booleans.get(MovingFlying.indexhorizontalCollision).booleanValue();
final boolean hasPos = booleans.get(MovingFlying.indexhasPos).booleanValue(); final boolean hasPos = booleans.get(MovingFlying.indexhasPos).booleanValue();
final boolean hasLook = booleans.get(MovingFlying.indexhasLook).booleanValue(); final boolean hasLook = booleans.get(MovingFlying.indexhasLook).booleanValue();
@@ -117,6 +117,7 @@ public class OutgoingPosition extends BaseAdapter {
} }
final CountableLocation packetData = data.teleportQueue.onOutgoingTeleport(x, y, z, yaw, pitch, teleportId); final CountableLocation packetData = data.teleportQueue.onOutgoingTeleport(x, y, z, yaw, pitch, teleportId);
data.recordOutgoingPosition(x, y, z, yaw, pitch, teleportId, packetData != null, time);
if (packetData == null) { if (packetData == null) {
// Add counter for untracked (by Bukkit API) outgoing teleport. // Add counter for untracked (by Bukkit API) outgoing teleport.
// TODO: There may be other cases which are indicated by Bukkit API events. // TODO: There may be other cases which are indicated by Bukkit API events.
@@ -24,6 +24,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -35,11 +36,12 @@ import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketAdapter;
//import com.viaversion.viaversion.api.Via;
import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.net.NetData; import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.checks.net.entity.ClientEntityTracker;
import fr.neatmonster.nocheatplus.checks.net.transaction.TransactionChannel;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.SchedulerHelper; import fr.neatmonster.nocheatplus.compat.SchedulerHelper;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion; import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
@@ -107,6 +109,9 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
return; return;
} }
StaticLog.logInfo("Adding packet level hooks for ProtocolLib (MC " + ProtocolLibrary.getProtocolManager().getMinecraftVersion().getVersion() + ")..."); StaticLog.logInfo("Adding packet level hooks for ProtocolLib (MC " + ProtocolLibrary.getProtocolManager().getMinecraftVersion().getVersion() + ")...");
// Transaction clock first, so the end-of-tick ping is registered before entity tracking.
register("fr.neatmonster.nocheatplus.checks.net.protocollib.TransactionAdapter", plugin);
register("fr.neatmonster.nocheatplus.checks.net.protocollib.EntityActivityAdapter", plugin);
//Special purpose. //Special purpose.
if (ConfigManager.isTrueForAnyConfig(ConfPaths.NET + ConfPaths.SUB_DEBUG) || ConfigManager.isTrueForAnyConfig(ConfPaths.CHECKS_DEBUG) ) { if (ConfigManager.isTrueForAnyConfig(ConfPaths.NET + ConfPaths.SUB_DEBUG) || ConfigManager.isTrueForAnyConfig(ConfPaths.CHECKS_DEBUG) ) {
// (Debug logging. Only activates if debug is set for checks or checks.net, not on the fly.) // (Debug logging. Only activates if debug is set for checks or checks.net, not on the fly.)
@@ -121,9 +126,7 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
// (Also sets lastKeepAliveTime, if enabled.) // (Also sets lastKeepAliveTime, if enabled.)
register("fr.neatmonster.nocheatplus.checks.net.protocollib.UseEntityAdapter", plugin); register("fr.neatmonster.nocheatplus.checks.net.protocollib.UseEntityAdapter", plugin);
} }
if (worldMan.isActiveAnywhere(CheckType.NET_FLYINGFREQUENCY) if (worldMan.isActiveAnywhere(CheckType.MOVING) || worldMan.isActiveAnywhere(CheckType.NET)) {
|| worldMan.isActiveAnywhere(CheckType.NET_MOVING)
|| worldMan.isActiveAnywhere(CheckType.NET_WRONGTURN)) {
// (Also sets lastKeepAliveTime, if enabled.) // (Also sets lastKeepAliveTime, if enabled.)
// If any check that uses flying packets is enabled, register all // If any check that uses flying packets is enabled, register all
register("fr.neatmonster.nocheatplus.checks.net.protocollib.MovingFlying", plugin); register("fr.neatmonster.nocheatplus.checks.net.protocollib.MovingFlying", plugin);
@@ -141,7 +144,7 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
register("fr.neatmonster.nocheatplus.checks.net.protocollib.CatchAllAdapter", plugin); register("fr.neatmonster.nocheatplus.checks.net.protocollib.CatchAllAdapter", plugin);
} }
} }
if (ServerVersion.compareMinecraftVersion("1.8") >= 0) { if (ServerVersion.isAtLeast("1.8")) {
// if (!BridgeMisc.hasIsUsingItemMethod()) { // if (!BridgeMisc.hasIsUsingItemMethod()) {
register("fr.neatmonster.nocheatplus.checks.net.protocollib.UseItemAdapter", plugin); register("fr.neatmonster.nocheatplus.checks.net.protocollib.UseItemAdapter", plugin);
// } // }
@@ -149,11 +152,15 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
} }
else { else {
// Fuck 1.7. // Fuck 1.7.
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.STATUS, "Disable UseItemAdapter due to incompatibilities. Using-item status won't be monitored."); NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.STATUS, "Disable UseItemAdapter and VelocityAdapter due to incompatibilities. Using-item status and explosion velocity won't be monitored.");
} }
if (worldMan.isActiveAnywhere(CheckType.NET_TOGGLEFREQUENCY)) { if (worldMan.isActiveAnywhere(CheckType.NET_TOGGLEFREQUENCY)) {
register("fr.neatmonster.nocheatplus.checks.net.protocollib.EntityActionAdapter", plugin); register("fr.neatmonster.nocheatplus.checks.net.protocollib.EntityActionAdapter", plugin);
} }
if (BridgeMisc.hasPlayerInputEvent()) {
register("fr.neatmonster.nocheatplus.checks.net.protocollib.InputsAdapter", plugin);
}
if (!registeredPacketAdapters.isEmpty()) { if (!registeredPacketAdapters.isEmpty()) {
List<String> names = new ArrayList<String>(registeredPacketAdapters.size()); List<String> names = new ArrayList<String>(registeredPacketAdapters.size());
for (PacketAdapter adapter : registeredPacketAdapters) { for (PacketAdapter adapter : registeredPacketAdapters) {
@@ -227,6 +234,7 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
} }
registeredPacketAdapters.clear(); registeredPacketAdapters.clear();
TransactionChannel.setSender(null);
} }
protected static void unregister(PacketAdapter adapter) { protected static void unregister(PacketAdapter adapter) {
@@ -252,23 +260,20 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
public void playerJoins(final Player player) { public void playerJoins(final Player player) {
if (!registeredPacketAdapters.isEmpty()) { if (!registeredPacketAdapters.isEmpty()) {
DataManager.getGenericInstance(player, NetData.class).onJoin(player); DataManager.getGenericInstance(player, NetData.class).onJoin(player);
DataManager.getGenericInstance(player, TransactionChannel.class).onJoin();
DataManager.getGenericInstance(player, ClientEntityTracker.class).onJoin(player);
} }
SchedulerHelper.runSyncDelayedTask(Bukkit.getPluginManager().getPlugin("NoCheatPlus"), (arg) -> {
// Ensure client-version checking doesn't yield UNKNOWN if multiprotocol plugins are not present on the server
if (Bukkit.getPluginManager().getPlugin("ViaVersion") == null && Bukkit.getPluginManager().getPlugin("ProtocolSupport") == null) {
// Assume clients to match the server protocol version - We do not support protocol-hack plugins other than PS/Via
// - If not even ProtocolLib is installed, do return UNKNOWN (that's on the server's owner). Time to make NCP full-on depend on ProtocolLib?
// - If the server has protocol plugins active but doesn't have CompatNoCheatPlus, that's on them as well :)
//DataManager.getPlayerData(player).setClientVersionID(Via.getAPI().getPlayerVersion(player.getUniqueId()));
DataManager.getPlayerData(player).setClientVersionID(ProtocolLibrary.getProtocolManager().getProtocolVersion(player));
}
}, 5);
} }
@Override @Override
public void playerLeaves(final Player player) { public void playerLeaves(final Player player) {
if (!registeredPacketAdapters.isEmpty()) { if (!registeredPacketAdapters.isEmpty()) {
DataManager.getGenericInstance(player, NetData.class).onLeave(player); DataManager.getGenericInstance(player, NetData.class).onLeave(player);
final IPlayerData pData = DataManager.getPlayerDataSafe(player);
if (pData != null) {
pData.getGenericInstance(ClientEntityTracker.class).onLeave();
pData.getGenericInstance(TransactionChannel.class).onLeave();
}
} }
// (On leaving the server, client data is reset in PlayerData) // (On leaving the server, client data is reset in PlayerData)
} }
@@ -279,6 +284,7 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
final Player player = event.getPlayer(); final Player player = event.getPlayer();
final NetData data = DataManager.getGenericInstance(player, NetData.class); final NetData data = DataManager.getGenericInstance(player, NetData.class);
data.onJoin(player); data.onJoin(player);
DataManager.getGenericInstance(player, ClientEntityTracker.class).invalidate();
final Location loc = event.getRespawnLocation(); final Location loc = event.getRespawnLocation();
data.teleportQueue.onTeleportEvent(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); data.teleportQueue.onTeleportEvent(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
} }
@@ -287,7 +293,6 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerTeleport(final PlayerTeleportEvent event) { public void onPlayerTeleport(final PlayerTeleportEvent event) {
if (!registeredPacketAdapters.isEmpty()) { if (!registeredPacketAdapters.isEmpty()) {
// TODO: Might move to MovingListener.
// TODO: Might still add cancelled UNKNOWN events. TEST IT // TODO: Might still add cancelled UNKNOWN events. TEST IT
final Location to = event.getTo(); final Location to = event.getTo();
if (to == null) { if (to == null) {
@@ -299,7 +304,20 @@ public class ProtocolLibComponent implements IDisableListener, INotifyReload, Jo
// Register expected location for comparison with outgoing packets. // Register expected location for comparison with outgoing packets.
data.teleportQueue.onTeleportEvent(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); data.teleportQueue.onTeleportEvent(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
data.clearFlyingQueue(); data.clearFlyingQueue();
if (event.getFrom() != null && event.getFrom().getWorld() != null && to.getWorld() != null
&& !event.getFrom().getWorld().getName().equals(to.getWorld().getName())) {
pData.getGenericInstance(ClientEntityTracker.class).invalidate();
}
} }
} }
@EventHandler(priority = EventPriority.MONITOR)
public void onWorldChange(final PlayerChangedWorldEvent event) {
if (!registeredPacketAdapters.isEmpty()) {
final IPlayerData pData = DataManager.getPlayerDataSafe(event.getPlayer());
if (pData != null) {
pData.getGenericInstance(ClientEntityTracker.class).invalidate();
}
}
}
} }
@@ -21,7 +21,6 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@@ -33,7 +32,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.net.NetConfig; import fr.neatmonster.nocheatplus.checks.net.NetConfig;
import fr.neatmonster.nocheatplus.compat.bukkit.BridgeBukkitAPI; import fr.neatmonster.nocheatplus.compat.registry.BukkitAPIAccessFactory;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion; import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.players.IPlayerData;
@@ -46,9 +45,9 @@ public class SoundDistance extends BaseAdapter {
/** Partly by debugging, partly from various sources, possibly including wrong spelling. */ /** Partly by debugging, partly from various sources, possibly including wrong spelling. */
private static final Set<String> effectNames = new HashSet<String>(Arrays.asList( private static final Set<String> effectNames = new HashSet<String>(Arrays.asList(
//////////// /////////////
// PRE 1.9 // PRE 1.9 //
//////////// /////////////
// Weather // Weather
"ambient.weather.thunder", "ambient.weather.thunder",
@@ -69,7 +68,7 @@ public class SoundDistance extends BaseAdapter {
"game.neutral.die", // Enderdragon 1.8.7 (debug). "game.neutral.die", // Enderdragon 1.8.7 (debug).
////////////////// //////////////////
// 1.9 AND LATER // 1.9 AND 1.20 //
////////////////// //////////////////
// Weather // Weather
"ENTITY_LIGHTNING_IMPACT", "ENTITY_LIGHTNING_IMPACT",
@@ -88,9 +87,22 @@ public class SoundDistance extends BaseAdapter {
"ENTITY_WITHER_DEATH", "ENTITY_WITHER_DEATH",
"ENTITY_WITHER_HURT", "ENTITY_WITHER_HURT",
"ENTITY_WITHER_SHOOT", "ENTITY_WITHER_SHOOT",
"ENTITY_WITHER_SPAWN" "ENTITY_WITHER_SPAWN",
////////////////////
// 1.21 AND LATER //
////////////////////
// Weather
"ENTITY_LIGHTNING_BOLT_IMPACT",
"ENTITY_LIGHTNING_BOLT_THUNDER",
// Enderdragon
"ENTITY_ENDER_DRAGON_AMBIENT",
"ENTITY_ENDER_DRAGON_DEATH",
"ENTITY_DRAGON_FIREBALL_EXPLODE",
"ENTITY_ENDER_DRAGON_FLAP",
"ENTITY_ENDER_DRAGON_GROWL",
"ENTITY_ENDER_DRAGON_HURT",
"ENTITY_ENDER_DRAGON_SHOOT"
)); ));
private final Integer idSoundEffectCancel = counters.registerKey("packet.sound.cancel"); private final Integer idSoundEffectCancel = counters.registerKey("packet.sound.cancel");
@@ -122,14 +134,7 @@ public class SoundDistance extends BaseAdapter {
} }
private boolean isSoundMonitoredLatest(final PacketContainer packetContainer) { private boolean isSoundMonitoredLatest(final PacketContainer packetContainer) {
StructureModifier<Sound> sounds = packetContainer.getSoundEffects(); return BukkitAPIAccessFactory.getBukkitAccess().matchSounds(packetContainer, effectNames);
for (final Sound sound : sounds.getValues()) {
if (sound != null && effectNames.contains(BridgeBukkitAPI.ConvertSoundToString(sound))) {
//debug(null, "MONITOR SOUND: " + sound);
return true;
}
}
return false;
} }
private boolean isSoundMonitored(final PacketContainer packetContainer) { private boolean isSoundMonitored(final PacketContainer packetContainer) {
@@ -0,0 +1,206 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.net.protocollib;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.net.entity.ClientEntityTracker;
import fr.neatmonster.nocheatplus.checks.net.transaction.TransactionChannel;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.components.registry.feature.TickListener;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData;
/**
* Sends the transaction ping and accepts only the replies this plugin wrote.
* One ping goes out at the end of each server tick so queued confirmations flush.
* Entity tracking uses that same clock.
*/
public class TransactionAdapter extends BaseAdapter implements TickListener {
private static final boolean USE_PING;
static {
boolean ping = false;
try {
ping = ServerVersion.isAtLeast("1.17") && PacketType.Play.Server.PING.isSupported();
}
catch (Throwable ignored) {}
USE_PING = ping;
}
private static PacketType[] listenTypes() {
List<PacketType> types = new ArrayList<PacketType>();
add(types, PacketType.Play.Client.PONG);
add(types, PacketType.Play.Client.TRANSACTION);
return types.toArray(new PacketType[types.size()]);
}
private static void add(List<PacketType> types, PacketType type) {
try {
if (type != null && type.isSupported()) {
types.add(type);
}
}
catch (Throwable ignored) {}
}
private int failures;
public TransactionAdapter(Plugin plugin) {
super(plugin, ListenerPriority.LOW, listenTypes());
TransactionChannel.setSender(new TransactionChannel.Sender() {
@Override
public boolean send(Player player, int wireId, boolean legacy) {
return write(player, wireId, legacy);
}
}, USE_PING);
NCPAPIProvider.getNoCheatPlusAPI().addComponent(this);
}
static boolean write(Player player, int wireId, boolean legacy) {
try {
PacketContainer packet;
if (!legacy) {
packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.PING);
packet.getIntegers().write(0, Integer.valueOf(wireId));
}
else {
packet = ProtocolLibrary.getProtocolManager().createPacket(PacketType.Play.Server.TRANSACTION);
packet.getIntegers().write(0, Integer.valueOf(0));
packet.getShorts().write(0, Short.valueOf((short) wireId));
packet.getBooleans().write(0, Boolean.FALSE);
}
// filters = false: this ping must not re-enter the listener that is sending it
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet, false);
return true;
}
catch (Throwable ignored) {
return false;
}
}
@Override
public void onTick(int tick, long timeLast) {
Player[] players = BridgeMisc.getOnlinePlayers();
for (int index = 0; index < players.length; index++) {
Player player = players[index];
if (player == null || !player.isOnline()) {
continue;
}
ClientEntityTracker tracker = tracker(player);
if (tracker != null) {
// Also writes this tick's ping, which confirms tasks queued on lastSent + 1.
tracker.onEndOfTick(player);
}
else {
TransactionChannel channel = channel(player);
if (channel != null) {
channel.send(player);
}
}
}
}
@Override
public void onPacketReceiving(PacketEvent event) {
if (!accept(event)) {
return;
}
try {
Player player = event.getPlayer();
TransactionChannel channel = channel(player);
if (channel == null) {
return;
}
PacketType type = event.getPacketType();
Integer id = readId(event.getPacket(), type);
if (id != null && channel.onReply(id.intValue())) {
event.setCancelled(true);
}
}
catch (Throwable throwable) {
fail(throwable);
}
}
@Override
public void onPacketSending(PacketEvent event) {
}
private static Integer readId(PacketContainer packet, PacketType type) {
try {
if (type == PacketType.Play.Client.PONG) {
return packet.getIntegers().readSafely(0);
}
Short action = packet.getShorts().readSafely(0);
return action == null ? null : Integer.valueOf(action.shortValue());
}
catch (Throwable ignored) {
return null;
}
}
private static TransactionChannel channel(Player player) {
if (player == null) {
return null;
}
IPlayerData data = DataManager.getPlayerDataSafe(player);
if (data == null) {
return null;
}
return data.getGenericInstance(TransactionChannel.class);
}
private static ClientEntityTracker tracker(Player player) {
if (player == null) {
return null;
}
IPlayerData data = DataManager.getPlayerDataSafe(player);
if (data == null) {
return null;
}
return data.getGenericInstance(ClientEntityTracker.class);
}
private static boolean accept(PacketEvent event) {
try {
if (event.isPlayerTemporary()) {
return false;
}
}
catch (NoSuchMethodError ignored) {}
return event.getPlayer() != null;
}
private void fail(Throwable throwable) {
if (failures++ < 3) {
StaticLog.logWarning("Transaction ping listener failed: " + throwable.getMessage());
}
}
}
@@ -32,6 +32,8 @@ import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.net.AttackFrequency; import fr.neatmonster.nocheatplus.checks.net.AttackFrequency;
import fr.neatmonster.nocheatplus.checks.net.NetConfig; import fr.neatmonster.nocheatplus.checks.net.NetConfig;
import fr.neatmonster.nocheatplus.checks.net.NetData; import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.ReflectionUtil; import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
@@ -40,6 +42,7 @@ import fr.neatmonster.nocheatplus.utilities.ReflectionUtil;
* Adapter for the UseEntity NMS packet * Adapter for the UseEntity NMS packet
*/ */
public class UseEntityAdapter extends BaseAdapter { public class UseEntityAdapter extends BaseAdapter {
private static final boolean isServerAtLeast1_13 = ServerVersion.isAtLeast("1.13");
private static class LegacyReflectionSet { private static class LegacyReflectionSet {
/** Hacks. */ /** Hacks. */
@@ -102,6 +105,10 @@ public class UseEntityAdapter extends BaseAdapter {
private final LegacyReflectionSet legacySet; private final LegacyReflectionSet legacySet;
private volatile boolean protocolLibEntityUseActionBroken = false;
private volatile boolean protocolLibEntityUseActionWarningLogged = false;
public UseEntityAdapter(Plugin plugin) { public UseEntityAdapter(Plugin plugin) {
super(plugin, PacketType.Play.Client.USE_ENTITY); super(plugin, PacketType.Play.Client.USE_ENTITY);
this.checkType = CheckType.NET_ATTACKFREQUENCY; this.checkType = CheckType.NET_ATTACKFREQUENCY;
@@ -167,12 +174,20 @@ public class UseEntityAdapter extends BaseAdapter {
if (!packetInterpreted) { if (!packetInterpreted) {
// Handle as if latest. // Handle as if latest.
try { try {
final StructureModifier<EntityUseAction> actions = packet.getEntityUseActions(); if (!protocolLibEntityUseActionBroken) {
final StructureModifier<WrappedEnumEntityUseAction> enumActions = packet.getEnumEntityUseActions(); final StructureModifier<EntityUseAction> actions = packet.getEntityUseActions();
if (actions.size() == 1 && actions.read(0) == EntityUseAction.ATTACK if (actions.size() == 1) {
|| enumActions.size() == 1 && enumActions.read(0).equals(WrappedEnumEntityUseAction.attack())) { packetInterpreted = true;
packetInterpreted = true; isAttack = actions.read(0) == EntityUseAction.ATTACK;
isAttack = true; }
// ProtocolLib compatibility: modern snapshots can throw while initializing enum wrappers.
if (!packetInterpreted && isServerAtLeast1_13) {
final StructureModifier<WrappedEnumEntityUseAction> enumActions = packet.getEnumEntityUseActions();
if (enumActions.size() == 1 && enumActions.read(0).equals(WrappedEnumEntityUseAction.attack())) {
packetInterpreted = true;
isAttack = true;
}
}
} }
} }
catch (NullPointerException e) { catch (NullPointerException e) {
@@ -181,6 +196,16 @@ public class UseEntityAdapter extends BaseAdapter {
* why doesn't the LegacyReflectionSet work here? * why doesn't the LegacyReflectionSet work here?
*/ */
} }
catch (LinkageError | RuntimeException e) {
protocolLibEntityUseActionBroken = true;
if (!protocolLibEntityUseActionWarningLogged) {
protocolLibEntityUseActionWarningLogged = true;
// Diagnostic logging: name the failing branch so attack/combat packet issues are clear in console.
StaticLog.logWarning("ProtocolLib could not expose USE_ENTITY action data (branch=enumEntityUseActions, packet=" + packet.getType() + "). Skipping AttackFrequency interpretation for USE_ENTITY packets until restart.");
StaticLog.logWarning("ProtocolLib USE_ENTITY action failure: " + e.getClass().getName() + ": " + e.getMessage());
}
return;
}
} }
if (!packetInterpreted) { if (!packetInterpreted) {
// StaticLog.logWarning("Attack packet couldn't be interpreted. Skipping AttackFrequency."); // StaticLog.logWarning("Attack packet couldn't be interpreted. Skipping AttackFrequency.");
@@ -350,6 +350,11 @@ public class UseItemAdapter extends BaseAdapter {
data.offHandUse = e.getHand() == EquipmentSlot.OFF_HAND; data.offHandUse = e.getHand() == EquipmentSlot.OFF_HAND;
return; return;
} }
// 1.21.11: Spears...
if (MaterialUtil.isSpear(m)) {
pData.setItemInUse(m);
data.offHandUse = e.getHand() == EquipmentSlot.OFF_HAND;
}
} }
else { else {
// No item in hands, no deal. // No item in hands, no deal.
@@ -17,15 +17,18 @@ package fr.neatmonster.nocheatplus.checks.net.protocollib;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.BukkitConverters;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
@@ -34,25 +37,28 @@ import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.players.IPlayerData;
/**
* Adapter for handling velocity related packet
*/
public class VelocityAdapter extends BaseAdapter { public class VelocityAdapter extends BaseAdapter {
private static PacketType[] initPacketTypes() { private static PacketType[] initPacketTypes() {
final List<PacketType> types = new LinkedList<PacketType>(Arrays.asList( final List<PacketType> types = new LinkedList<PacketType>(Arrays.asList(
PacketType.Play.Server.EXPLOSION PacketType.Play.Server.EXPLOSION,
//PacketType.Play.Server.ENTITY_VELOCITY PacketType.Play.Server.ENTITY_VELOCITY
)); ));
return types.toArray(new PacketType[types.size()]); return types.toArray(new PacketType[types.size()]);
} }
public VelocityAdapter(Plugin plugin) { public VelocityAdapter(Plugin plugin) {
super(plugin, ListenerPriority.MONITOR, initPacketTypes()); super(plugin, ListenerPriority.LOWEST, initPacketTypes());
} }
@Override @Override
public void onPacketSending(final PacketEvent event) { public void onPacketSending(final PacketEvent event) {
handleVelocityPacket(event); handleOutgoingVelocityPacket(event);
} }
private void handleVelocityPacket(PacketEvent event) { private void handleOutgoingVelocityPacket(PacketEvent event) {
try { try {
if (event.isPlayerTemporary()) return; if (event.isPlayerTemporary()) return;
} }
@@ -65,13 +71,51 @@ public class VelocityAdapter extends BaseAdapter {
return; return;
} }
} }
if (event.getPacketType() != PacketType.Play.Server.EXPLOSION) return; if (event.getPacketType() == PacketType.Play.Server.EXPLOSION) {
handleExplosionPacket(event);
} else {
handleEntityVelocityPacket(event);
}
}
private void handleEntityVelocityPacket(PacketEvent event) {
final Player player = event.getPlayer();
final PacketContainer packet = event.getPacket();
final StructureModifier<Integer> ints = packet.getIntegers();
Integer id = ints.readSafely(0);
id = (id == null) ? -1 : id;
final IPlayerData pData = DataManager.getPlayerData(player);
if (id != player.getEntityId() || !pData.isCheckActive(CheckType.MOVING, player)) return;
final MovingData data = pData.getGenericInstance(MovingData.class);
final MovingConfig cc = pData.getGenericInstance(MovingConfig.class);
if (ints.size() == 4) {
final Double velX = ints.read(1) / 8000D;
final Double velY = ints.read(2) / 8000D;
final Double velZ = ints.read(3) / 8000D;
data.addVelocity(player, cc, velX, velY, velZ);
} else {
final Vector vec = packet.getVectors().readSafely(0);
if (vec != null) {
data.addVelocity(player, cc, vec.getX(), vec.getY(), vec.getZ());
}
}
}
private void handleExplosionPacket(PacketEvent event) {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
final PacketContainer packet = event.getPacket(); final PacketContainer packet = event.getPacket();
final StructureModifier<Float> floats = packet.getFloat(); final StructureModifier<Float> floats = packet.getFloat();
final IPlayerData pData = DataManager.getPlayerData(player);
if (!pData.isCheckActive(CheckType.MOVING, player)) return;
final MovingData data = pData.getGenericInstance(MovingData.class);
final MovingConfig cc = pData.getGenericInstance(MovingConfig.class);
if (floats.size() != 4) { if (floats.size() != 4) {
// TODO : Warning Optional<Vector> read = packet.getOptionals(BukkitConverters.getVectorConverter()).read(0);
if (read.isPresent()) {
final Vector vec = read.get();
data.addVelocity(player, cc, vec.getX(), vec.getY(), vec.getZ(), VelocityFlags.ORIGIN_EXPLOSION | VelocityFlags.ADDITIVE);
}
return; return;
} }
@@ -81,10 +125,6 @@ public class VelocityAdapter extends BaseAdapter {
final Float velY = floats.read(2); final Float velY = floats.read(2);
final Float velZ = floats.read(3); final Float velZ = floats.read(3);
if (Math.abs(velX) == 0.0 && Math.abs(velZ) == 0.0 && Math.abs(velY) == 0.0) return; if (Math.abs(velX) == 0.0 && Math.abs(velZ) == 0.0 && Math.abs(velY) == 0.0) return;
final IPlayerData pData = DataManager.getPlayerData(player);
if (!pData.isCheckActive(CheckType.MOVING, player)) return;
final MovingData data = pData.getGenericInstance(MovingData.class);
final MovingConfig cc = pData.getGenericInstance(MovingConfig.class);
// Process velocity. // Process velocity.
data.addVelocity(player, cc, velX, velY, velZ, VelocityFlags.ORIGIN_EXPLOSION | VelocityFlags.ADDITIVE); data.addVelocity(player, cc, velX, velY, velZ, VelocityFlags.ORIGIN_EXPLOSION | VelocityFlags.ADDITIVE);
} }
+3 -2
View File
@@ -23,7 +23,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version> <version>26.3-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -33,8 +33,9 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>fr.neatmonster</groupId> <groupId>fr.neatmonster</groupId>
<artifactId>ncplegacy</artifactId> <artifactId>ncpbukkitapiaggregated</artifactId>
<version>1.1-SNAPSHOT</version> <version>1.1-SNAPSHOT</version>
<type>pom</type>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@@ -17,6 +17,7 @@ package fr.neatmonster.nocheatplus.actions.types;
import java.util.logging.Level; import java.util.logging.Level;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import fr.neatmonster.nocheatplus.actions.AbstractActionList; import fr.neatmonster.nocheatplus.actions.AbstractActionList;
@@ -63,8 +64,8 @@ public class CommandAction<D extends ParameterHolder, L extends AbstractActionLi
@Override @Override
public void execute(final D violationData) { public void execute(final D violationData) {
final String command = getMessage(violationData); final String command = getMessage(violationData);
SchedulerHelper.runSyncTask(plugin, (arg) -> { final Runnable runCommand = () -> {
try { try {
Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command); Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), command);
if (logDebug) { if (logDebug) {
debug(violationData, command); debug(violationData, command);
@@ -74,7 +75,20 @@ public class CommandAction<D extends ParameterHolder, L extends AbstractActionLi
StaticLog.logOnce(Level.WARNING, "Failed to execute the command '" + command + "': " + e.getMessage() StaticLog.logOnce(Level.WARNING, "Failed to execute the command '" + command + "': " + e.getMessage()
+ ", please check if everything is setup correct.", StringUtil.throwableToString(e)); + ", please check if everything is setup correct.", StringUtil.throwableToString(e));
} }
}); };
if (plugin == null) {
runCommand.run();
return;
}
if (SchedulerHelper.isFoliaServer() && violationData instanceof ViolationData) {
final Player player = ((ViolationData) violationData).player;
if (player != null) {
SchedulerHelper.runSyncTaskForEntity(player, plugin, (arg) -> runCommand.run(),
() -> SchedulerHelper.runSyncTask(plugin, (arg) -> runCommand.run()));
return;
}
}
SchedulerHelper.runSyncTask(plugin, (arg) -> runCommand.run());
} }
private void debug(final D violationData, String command) { private void debug(final D violationData, String command) {
@@ -64,7 +64,6 @@ public enum CheckType {
COMBINED(CheckTypeType.GROUP, CheckType.ALL, Permissions.COMBINED), COMBINED(CheckTypeType.GROUP, CheckType.ALL, Permissions.COMBINED),
COMBINED_IMPROBABLE(CheckTypeType.CHECK, COMBINED, Permissions.COMBINED_IMPROBABLE), COMBINED_IMPROBABLE(CheckTypeType.CHECK, COMBINED, Permissions.COMBINED_IMPROBABLE),
COMBINED_MUNCHHAUSEN(CheckTypeType.CHECK, COMBINED, Permissions.COMBINED_MUNCHHAUSEN),
/** Rather for data removal and exemption. */ /** Rather for data removal and exemption. */
COMBINED_YAWRATE(CheckTypeType.CHECK, COMBINED), COMBINED_YAWRATE(CheckTypeType.CHECK, COMBINED),
@@ -30,7 +30,6 @@ import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckListener; import fr.neatmonster.nocheatplus.checks.CheckListener;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.combined.CombinedConfig; import fr.neatmonster.nocheatplus.checks.combined.CombinedConfig;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.net.FlyingQueueHandle; import fr.neatmonster.nocheatplus.checks.net.FlyingQueueHandle;
import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying; import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying;
import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_13;
@@ -47,7 +46,6 @@ import fr.neatmonster.nocheatplus.players.PlayerFactoryArgument;
import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.utilities.CheckUtils; import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.entity.InventoryUtil; import fr.neatmonster.nocheatplus.utilities.entity.InventoryUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil; import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.map.BlockProperties; import fr.neatmonster.nocheatplus.utilities.map.BlockProperties;
import fr.neatmonster.nocheatplus.utilities.moving.MovingUtil; import fr.neatmonster.nocheatplus.utilities.moving.MovingUtil;
@@ -349,16 +347,10 @@ public class BlockInteractListener extends CheckListener {
&& event.isCancelled() && event.useItemInHand() != Result.DENY) { && event.isCancelled() && event.useItemInHand() != Result.DENY) {
final ItemStack stack = Bridge1_9.getUsedItem(player, event); final ItemStack stack = Bridge1_9.getUsedItem(player, event);
if (stack != null && BridgeMisc.maybeElytraBoost(player, stack.getType())) { if (stack != null && BridgeMisc.maybeElytraBoost(player, stack.getType())) {
final int power = BridgeMisc.getFireworksPower(stack); // The boost itself is the rocket entity the client still has. Its lifetime is random,
final MovingData mData = pData.getGenericInstance(MovingData.class); // so a right-click timer does not match the ticks the client actually boosts.
final int ticks = Math.max((1 + power) * 20, 30);
mData.fireworksBoostDuration = ticks;
mData.fireworksBoostTickNeedCheck = ticks - 1;
// Expiration tick: not general latency, rather a minimum margin for sudden congestion.
mData.fireworksBoostTickExpire = TickTask.getTick() + ticks;
// TODO: Invalidation mechanics: by tick/time well ?
if (pData.isDebugActive(CheckType.MOVING)) { if (pData.isDebugActive(CheckType.MOVING)) {
debug(player, "Elytra boost (power " + power + "): " + stack); debug(player, "Elytra boost item: " + stack);
} }
} }
} }
@@ -138,6 +138,7 @@ public class Visible extends Check {
// Initialize. // Initialize.
final BlockCache blockCache = this.wrapBlockCache.getBlockCache(); final BlockCache blockCache = this.wrapBlockCache.getBlockCache();
blockCache.setAccess(loc.getWorld()); blockCache.setAccess(loc.getWorld());
blockCache.setPlayerData(pData);
rayTracing.setBlockCache(blockCache); rayTracing.setBlockCache(blockCache);
//collides = !checker.checkFlyingQueue(eyeX, eyeY, eyeZ, loc.getYaw(), loc.getPitch(), //collides = !checker.checkFlyingQueue(eyeX, eyeY, eyeZ, loc.getYaw(), loc.getPitch(),
// blockX, blockY, blockZ, flyingHandle, face, tags, debug, player); // blockX, blockY, blockZ, flyingHandle, face, tags, debug, player);
@@ -160,7 +161,14 @@ public class Visible extends Check {
else if (Math.abs(face.getModZ()) > 0) { else if (Math.abs(face.getModZ()) > 0) {
axisData.priority = Axis.Z_AXIS; axisData.priority = Axis.Z_AXIS;
} }
/**
* Origin version of updated Visible check, also implement for Fight.Visiable.
* After lots of reports telling that randomly flagged with old one, this was made.
* Rather fixing look after interact packets, hidden micro-moves for in sight check.
* This just check with obstacles from player's eyes to the block they are interacting. Is it possible at all?
* Since it doesn't have correct line of sight, handle special blocks like stairs can be problematic.
* Bedrock Edition compatible! Players playing on touch screen and interact blocks ~60 degree off from their look.
*/
do { do {
canContinue = false; canContinue = false;
for (BlockCoord neighbor : MapUtil.getNeighborsInDirection(bc, direction, eyeX, eyeY, eyeZ, axisData)) { for (BlockCoord neighbor : MapUtil.getNeighborsInDirection(bc, direction, eyeX, eyeY, eyeZ, axisData)) {
@@ -199,15 +199,14 @@ public class BlockPlaceListener extends CheckListener {
placedMat = BlockProperties.isAir(event.getItemInHand()) ? Material.AIR : event.getItemInHand().getType(); placedMat = BlockProperties.isAir(event.getItemInHand()) ? Material.AIR : event.getItemInHand().getType();
} }
else placedMat = Bridge1_9.getItemInMainHand(player).getType(); // Safety first. else placedMat = Bridge1_9.getItemInMainHand(player).getType(); // Safety first.
boolean cancelled = false; boolean cancelled = false;
int skippedRedundantChecks = 0; int skippedRedundantChecks = 0;
final IPlayerData pData = DataManager.getPlayerData(player); final IPlayerData pData = DataManager.getPlayerData(player);
final BlockPlaceData data = pData.getGenericInstance(BlockPlaceData.class); final BlockPlaceData data = pData.getGenericInstance(BlockPlaceData.class);
final BlockPlaceConfig cc = pData.getGenericInstance(BlockPlaceConfig.class); final BlockPlaceConfig cc = pData.getGenericInstance(BlockPlaceConfig.class);
final BlockInteractData bdata = pData.getGenericInstance(BlockInteractData.class); final BlockInteractData biData = pData.getGenericInstance(BlockInteractData.class);
// isInteractBlock - the block placed against is the block last interacted with. // isInteractBlock - the block placed against is the block last interacted with.
final boolean isInteractBlock = !bdata.getLastIsCancelled() && bdata.matchesLastBlock(TickTask.getTick(), blockAgainst); final boolean isInteractBlock = !biData.getLastIsCancelled() && biData.matchesLastBlock(TickTask.getTick(), blockAgainst);
final BlockFace placedFace = event.getBlock().getFace(blockAgainst); final BlockFace placedFace = event.getBlock().getFace(blockAgainst);
final Block blockPlaced = event.getBlockPlaced(); final Block blockPlaced = event.getBlockPlaced();
final boolean shouldSkipSome; final boolean shouldSkipSome;
@@ -260,17 +259,14 @@ public class BlockPlaceListener extends CheckListener {
} }
// Check for Improbable, whatever FastPlace says, provided the feature is enabled at all. // Check for Improbable, whatever FastPlace says, provided the feature is enabled at all.
if (cc.fastPlaceImprobableWeight > 0.0f) { if (cc.fastPlaceImprobableWeight > 0.0f) {
// Check only if frequency is decently high. if (cc.fastPlaceImprobableFeedOnly) {
if (data.fastPlaceVL > 20) { Improbable.feed(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis());
// Don't check if set to only feed.
if (!cc.fastPlaceImprobableFeedOnly) {
if (Improbable.check(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis(), "blockplace.fastplace", pData)) {
cancelled = true;
}
}
else Improbable.feed(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis());
} }
// Feed only for lower frequencies. else if (data.fastPlaceVL > 25 && Improbable.check(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis(), "blockplace.fastplace", pData)) {
// Only check on decently high frequencies
cancelled = true;
}
// Feed for lower frequencies.
else Improbable.feed(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis()); else Improbable.feed(player, cc.fastPlaceImprobableWeight, System.currentTimeMillis());
} }
} }
@@ -316,7 +312,7 @@ public class BlockPlaceListener extends CheckListener {
final double eyeHeight = MovingUtil.getEyeHeight(player); final double eyeHeight = MovingUtil.getEyeHeight(player);
// Reach check (distance). // Reach check (distance).
if (!cancelled && !shouldSkipSome) { if (!cancelled && !shouldSkipSome) {
if (isInteractBlock && bdata.isPassedCheck(CheckType.BLOCKINTERACT_REACH)) { if (isInteractBlock && biData.isPassedCheck(CheckType.BLOCKINTERACT_REACH)) {
skippedRedundantChecks++; skippedRedundantChecks++;
} }
else if (reachCheck && reach.check(player, eyeHeight, block, data, cc)) { else if (reachCheck && reach.check(player, eyeHeight, block, data, cc)) {
@@ -325,7 +321,7 @@ public class BlockPlaceListener extends CheckListener {
} }
// Direction check. // Direction check.
if (!cancelled && !shouldSkipSome) { if (!cancelled && !shouldSkipSome) {
if (isInteractBlock && bdata.isPassedCheck(CheckType.BLOCKINTERACT_DIRECTION)) { if (isInteractBlock && biData.isPassedCheck(CheckType.BLOCKINTERACT_DIRECTION)) {
skippedRedundantChecks++; skippedRedundantChecks++;
} }
else if (directionCheck) { else if (directionCheck) {
@@ -507,7 +503,7 @@ public class BlockPlaceListener extends CheckListener {
} }
else { else {
final Material mat = player.getLocation(useLoc2).getBlock().getType(); final Material mat = player.getLocation(useLoc2).getBlock().getType();
final long flags = BlockFlags.F_CLIMBABLE | BlockFlags.F_LIQUID | BlockFlags.F_IGN_PASSABLE; final long flags = BlockFlags.F_CLIMBABLE | BlockFlags.F_LIQUID | BlockFlags.F_IGN_PASSABLE_CHECK;
if (!BlockProperties.isAir(mat) && (BlockFlags.getBlockFlags(mat) & flags) == 0 && !mcAccess.getHandle().hasGravity(mat)) { if (!BlockProperties.isAir(mat) && (BlockFlags.getBlockFlags(mat) & flags) == 0 && !mcAccess.getHandle().hasGravity(mat)) {
// Still fails on piston traps etc. // Still fails on piston traps etc.
if (!BlockProperties.isPassable(player.getLocation(), projectile.getLocation()) if (!BlockProperties.isPassable(player.getLocation(), projectile.getLocation())
@@ -31,6 +31,7 @@ import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckListener; import fr.neatmonster.nocheatplus.checks.CheckListener;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.checks.net.NetData;
import fr.neatmonster.nocheatplus.command.CommandUtil; import fr.neatmonster.nocheatplus.command.CommandUtil;
import fr.neatmonster.nocheatplus.compat.BridgeMisc; import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.SchedulerHelper; import fr.neatmonster.nocheatplus.compat.SchedulerHelper;
@@ -162,6 +163,16 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
} }
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerTeleportCommandMonitor(final PlayerCommandPreprocessEvent event) {
final String command = getTeleportLikeCommand(event.getMessage());
if (command != null) {
final Player player = event.getPlayer();
DataManager.getPlayerData(player).getGenericInstance(NetData.class)
.recordTeleportCommand(command, player.getLocation(), System.currentTimeMillis());
}
}
/** We listen to PlayerCommandPreprocess events because commands can be used for spamming too. */ /** We listen to PlayerCommandPreprocess events because commands can be used for spamming too. */
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) { public void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) {
@@ -226,6 +237,45 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe
} }
} }
private String getTeleportLikeCommand(final String message) {
if (message == null) {
return null;
}
final String lcMessage = StringUtil.leftTrim(message).toLowerCase();
if (lcMessage.length() < 2 || lcMessage.charAt(0) != '/') {
return null;
}
final String[] split = lcMessage.split(" ", 2);
String command = split[0].substring(1);
final int namespaceIndex = command.indexOf(':');
if (namespaceIndex >= 0 && namespaceIndex + 1 < command.length()) {
command = command.substring(namespaceIndex + 1);
}
return isTeleportLikeCommand(command) ? command : null;
}
private boolean isTeleportLikeCommand(final String command) {
return command.equals("rtp")
|| command.equals("randomtp")
|| command.equals("randomteleport")
|| command.equals("wild")
|| command.equals("wilderness")
|| command.equals("spawn")
|| command.equals("hub")
|| command.equals("lobby")
|| command.equals("home")
|| command.equals("homes")
|| command.equals("warp")
|| command.equals("warps")
|| command.equals("back")
|| command.equals("tpa")
|| command.equals("tpaccept")
|| command.equals("tpahere")
|| command.equals("tp")
|| command.equals("teleport")
|| command.equals("tphere");
}
private boolean checkUntrackedLocation(final Player player, final String message, private boolean checkUntrackedLocation(final Player player, final String message,
final MovingConfig mcc, final IPlayerData pData) { final MovingConfig mcc, final IPlayerData pData) {
final Location loc = player.getLocation(useLoc); final Location loc = player.getLocation(useLoc);
@@ -52,8 +52,6 @@ public class CombinedConfig extends ACheckConfig {
public final boolean invulnerableTriggerAlways; public final boolean invulnerableTriggerAlways;
public final boolean invulnerableTriggerFallDistance; public final boolean invulnerableTriggerFallDistance;
public final ActionList munchHausenActions;
// Last yaw tracking // Last yaw tracking
public final float yawRate; public final float yawRate;
public final boolean yawRateImprobableFeedOnly; public final boolean yawRateImprobableFeedOnly;
@@ -109,8 +107,6 @@ public class CombinedConfig extends ACheckConfig {
invulnerableTriggerAlways = config.getBoolean(ConfPaths.COMBINED_INVULNERABLE_TRIGGERS_ALWAYS); invulnerableTriggerAlways = config.getBoolean(ConfPaths.COMBINED_INVULNERABLE_TRIGGERS_ALWAYS);
invulnerableTriggerFallDistance = config.getBoolean(ConfPaths.COMBINED_INVULNERABLE_TRIGGERS_FALLDISTANCE); invulnerableTriggerFallDistance = config.getBoolean(ConfPaths.COMBINED_INVULNERABLE_TRIGGERS_FALLDISTANCE);
munchHausenActions = config.getOptimizedActionList(ConfPaths.COMBINED_MUNCHHAUSEN_ACTIONS, Permissions.COMBINED_MUNCHHAUSEN);
yawRate = config.getInt(ConfPaths.COMBINED_YAWRATE_RATE); yawRate = config.getInt(ConfPaths.COMBINED_YAWRATE_RATE);
yawRateImprobableFeedOnly = config.getBoolean(ConfPaths.COMBINED_YAWRATE_IMPROBABLE_FEEDONLY); yawRateImprobableFeedOnly = config.getBoolean(ConfPaths.COMBINED_YAWRATE_IMPROBABLE_FEEDONLY);
yawRateImprobableWeight = (float) config.getDouble(ConfPaths.COMBINED_YAWRATE_IMPROBABLE_WEIGHT); yawRateImprobableWeight = (float) config.getDouble(ConfPaths.COMBINED_YAWRATE_IMPROBABLE_WEIGHT);
@@ -19,6 +19,7 @@ import java.util.Collection;
import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.access.ACheckData; import fr.neatmonster.nocheatplus.checks.access.ACheckData;
import fr.neatmonster.nocheatplus.components.data.IDataOnRemoveSubCheckData; import fr.neatmonster.nocheatplus.components.data.IDataOnRemoveSubCheckData;
import fr.neatmonster.nocheatplus.players.PlayerData;
import fr.neatmonster.nocheatplus.utilities.PenaltyTime; import fr.neatmonster.nocheatplus.utilities.PenaltyTime;
import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency; import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency;
@@ -26,12 +27,26 @@ public class CombinedData extends ACheckData implements IDataOnRemoveSubCheckDat
// VLs // VLs
public double improbableVL = 0; public double improbableVL = 0;
public double munchHausenVL = 0;
// Invulnerable management: // Invulnerable management:
/** This is the tick from which on the player is vulnerable again. */ /** This is the tick from which on the player is vulnerable again. */
public int invulnerableTick = Integer.MIN_VALUE; public int invulnerableTick = Integer.MIN_VALUE;
/**
* Last sprinting state of the player as set by {@link PlayerData#isSprinting()}. Set in SurvivalFly.
*/
public boolean wasSprinting;
/**
* Last shift state of the player as set by {@link PlayerData#isShiftKeyPressed()}. Set in SurvivalFly.
*/
public boolean wasPressingShift;
/** Last time the player was levitating */
public boolean wasLevitating;
/** Last time the player was slow falling */
public boolean wasSlowFalling;
// Yawrate check. // Yawrate check.
public float lastYaw; public float lastYaw;
public long lastYawTime; public long lastYawTime;
@@ -46,7 +61,7 @@ public class CombinedData extends ACheckData implements IDataOnRemoveSubCheckDat
public final PenaltyTime timeFreeze = new PenaltyTime(); public final PenaltyTime timeFreeze = new PenaltyTime();
// Improbable check // Improbable check
public final ActionFrequency improbableCount = new ActionFrequency(20, 3000); // 20 buckets covering 3 seconds each (full time resolution of 1 minute) public final ActionFrequency improbableCount = new ActionFrequency(20, 3000); // Sliding window of 20 buckets, each 3 seconds long (total span: ~1 minute, with 3s resolution)
// *----------No slowdown related data----------* // *----------No slowdown related data----------*
/** Whether the player use the item on left hand */ /** Whether the player use the item on left hand */
@@ -57,8 +72,6 @@ public class CombinedData extends ACheckData implements IDataOnRemoveSubCheckDat
public long releaseItemTime = 0; public long releaseItemTime = 0;
/** Detection flag */ /** Detection flag */
public boolean isHackingRI = false; public boolean isHackingRI = false;
/** Detection flag */
public boolean invalidItemUse = false;
/** /**
* Reduce Improbable's data by the given amount, capped at a minimum of 0. * Reduce Improbable's data by the given amount, capped at a minimum of 0.
@@ -88,9 +101,6 @@ public class CombinedData extends ACheckData implements IDataOnRemoveSubCheckDat
case COMBINED_YAWRATE: case COMBINED_YAWRATE:
yawFreq.clear(System.currentTimeMillis()); // TODO: Document there, which to use. yawFreq.clear(System.currentTimeMillis()); // TODO: Document there, which to use.
break; break;
case COMBINED_MUNCHHAUSEN:
munchHausenVL = 0;
break;
case COMBINED: case COMBINED:
return true; return true;
default: default:
@@ -23,20 +23,23 @@ import org.bukkit.entity.Pose;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityPoseChangeEvent; import org.bukkit.event.entity.EntityPoseChangeEvent;
import org.bukkit.event.entity.EntityToggleGlideEvent; import org.bukkit.event.entity.EntityToggleGlideEvent;
import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerRiptideEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.event.player.PlayerToggleSprintEvent; import org.bukkit.event.player.PlayerToggleSprintEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckListener; import fr.neatmonster.nocheatplus.checks.CheckListener;
@@ -46,10 +49,12 @@ import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData; import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveInfo; import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveInfo;
import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags; import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.compat.AlmostBoolean;
import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_13;
import fr.neatmonster.nocheatplus.compat.Bridge1_9; import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeMisc; import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.versions.ClientVersion; import fr.neatmonster.nocheatplus.compat.versions.ClientVersion;
import fr.neatmonster.nocheatplus.compat.versions.ServerVersion;
import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.data.ICheckData; import fr.neatmonster.nocheatplus.components.data.ICheckData;
import fr.neatmonster.nocheatplus.components.data.IData; import fr.neatmonster.nocheatplus.components.data.IData;
@@ -73,12 +78,11 @@ import fr.neatmonster.nocheatplus.worlds.WorldFactoryArgument;
* @author asofold * @author asofold
* *
*/ */
@SuppressWarnings("UnstableApiUsage")
public class CombinedListener extends CheckListener implements JoinLeaveListener { public class CombinedListener extends CheckListener implements JoinLeaveListener {
protected final Improbable improbable = addCheck(new Improbable()); protected final Improbable improbable = addCheck(new Improbable());
protected final MunchHausen munchHausen = addCheck(new MunchHausen());
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class); private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final int idFakeInvulnerable = counters.registerKey("fakeinvulnerable"); private final int idFakeInvulnerable = counters.registerKey("fakeinvulnerable");
@@ -115,7 +119,7 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
// If -for whatever reason- the event is not available, handle toggle gliding with PMEs // If -for whatever reason- the event is not available, handle toggle gliding with PMEs
queuedComponents.add(new Listener() { queuedComponents.add(new Listener() {
// We can ignore the event here, if cancelled, since the player will not be able to move anyway // We can ignore the event here, if cancelled, since the player will not be able to move anyway
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onEventlessToggleGlide(final PlayerMoveEvent event) { public void onEventlessToggleGlide(final PlayerMoveEvent event) {
final PlayerMoveData lastMove = DataManager.getPlayerData(event.getPlayer()).getGenericInstance(MovingData.class).playerMoves.getFirstPastMove(); final PlayerMoveData lastMove = DataManager.getPlayerData(event.getPlayer()).getGenericInstance(MovingData.class).playerMoves.getFirstPastMove();
final PlayerMoveData thisMove = DataManager.getPlayerData(event.getPlayer()).getGenericInstance(MovingData.class).playerMoves.getCurrentMove(); final PlayerMoveData thisMove = DataManager.getPlayerData(event.getPlayer()).getGenericInstance(MovingData.class).playerMoves.getCurrentMove();
@@ -130,12 +134,20 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
} }
if (BridgeMisc.hasEntityChangePoseEvent()) { if (BridgeMisc.hasEntityChangePoseEvent()) {
queuedComponents.add(new Listener() { queuedComponents.add(new Listener() {
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.MONITOR)
public void onChangingPose(final EntityPoseChangeEvent event) { public void onChangingPose(final EntityPoseChangeEvent event) {
handlePoseChangeEvent(event.getEntity(), event.getPose()); handlePoseChangeEvent(event.getEntity(), event.getPose());
} }
}); });
} }
if (Bridge1_13.hasPlayerRiptideEvent()) {
queuedComponents.add(new Listener() {
@EventHandler(priority = EventPriority.MONITOR)
public void onTridentRelease(final PlayerRiptideEvent event) {
onReleasingTrident(event.getPlayer(), event.getVelocity().clone());
}
});
}
// Register Data, Listener and Config. // Register Data, Listener and Config.
api.register(api.newRegistrationContext() api.register(api.newRegistrationContext()
@@ -163,6 +175,18 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
); );
} }
/**
* Sets whether the player has released a trident with riptide on, which will propel the player in air or water.
*
* @param player
* @param vel Riptide velocity.
*/
public void onReleasingTrident(Player player, Vector vel) {
final IPlayerData pData = DataManager.getPlayerData(player);
final MovingData data = pData.getGenericInstance(MovingData.class);
data.setTridentReleaseEvent(AlmostBoolean.MAYBE);
}
/** /**
* Judge if the player can effectively start to glide. * Judge if the player can effectively start to glide.
* *
@@ -256,7 +280,7 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
* (see: {@code LocalPlayer.java -> aiStep() -> isMovingSlowly() -> isVisuallyCrawling()}).</li> * (see: {@code LocalPlayer.java -> aiStep() -> isMovingSlowly() -> isVisuallyCrawling()}).</li>
* </ul> * </ul>
* <p> * <p>
* Therefore, Bukkit's methods/event can no longer be relied upon to determine if a player should have reduced movement speed, as * Therefore, Bukkit's methods/event can no longer be relied upon to determine if a player should move slower, as
* they are associated with shift key presses and not player poses; and we need to know this for the horizontal speed prediction * they are associated with shift key presses and not player poses; and we need to know this for the horizontal speed prediction
* </p> * </p>
* <p> * <p>
@@ -280,16 +304,77 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
final MovingConfig cc = pData.getGenericInstance(MovingConfig.class); final MovingConfig cc = pData.getGenericInstance(MovingConfig.class);
if (newPose.equals(Pose.SWIMMING) && !BlockProperties.isInWater(player, player.getLocation(), cc.yOnGround)) { if (newPose.equals(Pose.SWIMMING) && !BlockProperties.isInWater(player, player.getLocation(), cc.yOnGround)) {
// isVisuallyCrawling()... // isVisuallyCrawling()...
pData.setIsInCrouchingPoseState(true); pData.setCrouching(true);
return; return;
} }
if (newPose.equals(Pose.SNEAKING)) { if (newPose.equals(Pose.SNEAKING)) {
// Sneaking... // Sneaking...
pData.setIsInCrouchingPoseState(true); pData.setCrouching(true);
return; return;
} }
// Entered another pose... // Entered another pose...
pData.setIsInCrouchingPoseState(false); pData.setCrouching(false);
}
private final boolean isLowerthan1_7 = ServerVersion.isLowerThan("1.7");
private final boolean isLowerthan1_8 = ServerVersion.isLowerThan("1.8");
private final boolean isLowerthan1_9 = ServerVersion.isLowerThan("1.9");
private final boolean isLowerthan1_10 = ServerVersion.isLowerThan("1.10");
private final boolean isLowerthan1_11_1 = ServerVersion.isLowerThan("1.11.1");
private final boolean isB1_11_2_1_12_1 = ServerVersion.isMinecraftVersionBetween("1.11.1", true, "1.12.2", false);
private final boolean isLowerthan1_13 = ServerVersion.isLowerThan("1.13");
private final boolean isLowerthan1_14 = ServerVersion.isLowerThan("1.14");
private final boolean isLowerthan1_15 = ServerVersion.isLowerThan("1.15");
private final boolean isLowerthan1_18 = ServerVersion.isLowerThan("1.18");
private final boolean is1_18 = ServerVersion.isMinecraftVersionBetween("1.18", true, "1.18.2", false);
private final boolean isLowerthan1_20 = ServerVersion.isLowerThan("1.20");
private final boolean isB1_20_1_20_5 = ServerVersion.isMinecraftVersionBetween("1.20", true, "1.20.6", false);
private final boolean isLowerthan1_21_2 = ServerVersion.isLowerThan("1.21.2");
private ClientVersion configurePlayerVersion(final IPlayerData pData, final boolean debug) {
if (isLowerthan1_7) {
return ClientVersion.LOWER_THAN_KNOWN_VERSIONS;
}
if (isLowerthan1_8) {
return ClientVersion.V_1_7_10;
}
if (isLowerthan1_9) {
return ClientVersion.V_1_8;
}
if (isLowerthan1_10) {
return ClientVersion.V_1_9;
}
if (isLowerthan1_11_1) {
return ClientVersion.V_1_10;
}
if (isB1_11_2_1_12_1) {
return ClientVersion.V_1_11_1;
}
if (isLowerthan1_13) {
return ClientVersion.V_1_12_2;
}
if (isLowerthan1_14) {
return ClientVersion.V_1_13;
}
if (isLowerthan1_15) {
return ClientVersion.V_1_14;
}
if (isLowerthan1_18) {
return ClientVersion.V_1_17;
}
if (is1_18) {
return ClientVersion.V_1_18;
}
if (isLowerthan1_20) {
return ClientVersion.V_1_18_2;
}
if (isB1_20_1_20_5) {
return ClientVersion.V_1_20;
}
if (isLowerthan1_21_2) {
return ClientVersion.V_1_20_6;
}
return ClientVersion.HIGHER_THAN_KNOWN_VERSIONS;
} }
@Override @Override
@@ -298,6 +383,8 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
final CombinedData data = pData.getGenericInstance(CombinedData.class); final CombinedData data = pData.getGenericInstance(CombinedData.class);
final CombinedConfig cc = pData.getGenericInstance(CombinedConfig.class); final CombinedConfig cc = pData.getGenericInstance(CombinedConfig.class);
final boolean debug = pData.isDebugActive(checkType); final boolean debug = pData.isDebugActive(checkType);
// NCP will first set the ID version. If: protocolsupport/viaversion, cncp are all present, then CNCP will override the version set by protocolsupport/viaversion.
pData.setClientVersionID(configurePlayerVersion(pData, debug).getProtocolVersion());
if (cc.invulnerableCheck if (cc.invulnerableCheck
&& (cc.invulnerableTriggerAlways || cc.invulnerableTriggerFallDistance && player.getFallDistance() > 0)) { && (cc.invulnerableTriggerAlways || cc.invulnerableTriggerFallDistance && player.getFallDistance() > 0)) {
// TODO: maybe make a heuristic for small fall distances with ground under feet (prevents future abuse with jumping) ? // TODO: maybe make a heuristic for small fall distances with ground under feet (prevents future abuse with jumping) ?
@@ -317,11 +404,27 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
if (BridgeMisc.hasEntityChangePoseEvent()) { if (BridgeMisc.hasEntityChangePoseEvent()) {
if (player.getPose().equals(Pose.SWIMMING) && !BlockProperties.isInWater(player, player.getLocation(), mCC.yOnGround)) { if (player.getPose().equals(Pose.SWIMMING) && !BlockProperties.isInWater(player, player.getLocation(), mCC.yOnGround)) {
// isVisuallyCrawling()... // isVisuallyCrawling()...
pData.setIsInCrouchingPoseState(true); pData.setCrouching(true);
} }
else if (player.getPose().equals(Pose.SNEAKING)) { else if (player.getPose().equals(Pose.SNEAKING)) {
// Sneaking... // Sneaking...
pData.setIsInCrouchingPoseState(true); pData.setCrouching(true);
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onLeftClickingBlocksOrAir(final PlayerInteractEvent event) {
final Player player = event.getPlayer();
if (event.getAction() != Action.LEFT_CLICK_AIR && event.getAction() != Action.LEFT_CLICK_BLOCK) {
return;
}
if (BridgeMisc.mayLungeForward(player)) {
final IPlayerData pData = DataManager.getPlayerData(player);
final PlayerMoveData thisMove = pData.getGenericInstance(MovingData.class).playerMoves.getCurrentMove();
thisMove.lungingForward = true;
if (pData.isDebugActive(CheckType.MOVING)) {
debug(player, "Set lunging forward flag in this move.");
} }
} }
} }
@@ -344,7 +447,7 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
final CombinedData data = pData.getGenericInstance(CombinedData.class); final CombinedData data = pData.getGenericInstance(CombinedData.class);
data.resetImprobableData(); data.resetImprobableData();
pData.setSprintingState(false); pData.setSprintingState(false);
pData.setIsInCrouchingPoseState(false); pData.setCrouching(false);
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
@@ -377,23 +480,22 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
// TODO: Actually, do legacy players (i.e.: 1.9 player) fire EntityPoseChangeEvent on newer servers? // TODO: Actually, do legacy players (i.e.: 1.9 player) fire EntityPoseChangeEvent on newer servers?
if (!event.isSneaking()) { if (!event.isSneaking()) {
// Player was sneaking and they now toggled it off. // Player was sneaking and they now toggled it off.
pData.setIsInCrouchingPoseState(false); pData.setCrouching(false);
return; return;
} }
if (Bridge1_13.isSwimming(event.getPlayer()) || Bridge1_9.isGliding(event.getPlayer()) || Bridge1_13.isRiptiding(event.getPlayer())) { if (Bridge1_13.isSwimming(event.getPlayer()) || Bridge1_9.isGliding(event.getPlayer()) || Bridge1_13.isRiptiding(event.getPlayer())) {
// Bukkit's ambiguous "isSneaking()" method would return true for all these cases, but like we've said above, sneaking is determined by player poses, not shift key presses. Just ignore. // Bukkit's ambiguous "isShift()" method would return true for all these cases, but like we've said above, sneaking is determined by player poses, not shift key presses. Just ignore.
pData.setIsInCrouchingPoseState(false); pData.setCrouching(false);
return; return;
} }
// Legacy clients can only enter the CROUCHING pose if they press the shift key, so in this case shifting does equal sneaking. // Legacy clients can only enter the CROUCHING pose if they press the shift key, so in this case shifting does equal sneaking.
pData.setIsInCrouchingPoseState(true); pData.setCrouching(true);
} }
/** NOTE: Cancelling does nothing. It won't stop players from sprinting. So, don't ignore cancelled events. */ /** NOTE: Cancelling does nothing. It won't stop players from sprinting. So, don't ignore cancelled events. */
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onToggleSprint(final PlayerToggleSprintEvent event) { public void onToggleSprint(final PlayerToggleSprintEvent event) {
final IPlayerData pData = DataManager.getPlayerData(event.getPlayer()); final IPlayerData pData = DataManager.getPlayerData(event.getPlayer());
final PlayerMoveData lastMove = pData.getGenericInstance(MovingData.class).playerMoves.getFirstPastMove();
if (!event.isSprinting()) { if (!event.isSprinting()) {
// Player was sprinting and they now toggled it off. // Player was sprinting and they now toggled it off.
pData.setSprintingState(false); pData.setSprintingState(false);
@@ -471,8 +573,14 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
aux.returnPlayerMoveInfo(moveInfo); aux.returnPlayerMoveInfo(moveInfo);
return; return;
} }
final PlayerMoveData thisMove = data.playerMoves.getCurrentMove();
if (BridgeMisc.mayLungeForward(player)) {
thisMove.lungingForward = true;
if (pData.isDebugActive(CheckType.MOVING)) {
debug(player, "Set lunging forward flag in this move.");
}
}
if (pData.isSprinting() || !BlockProperties.isAir(stack) && stack.getEnchantmentLevel(Enchantment.KNOCKBACK) > 0) { if (pData.isSprinting() || !BlockProperties.isAir(stack) && stack.getEnchantmentLevel(Enchantment.KNOCKBACK) > 0) {
final PlayerMoveData thisMove = data.playerMoves.getCurrentMove();
thisMove.hasAttackSlowDown = true; thisMove.hasAttackSlowDown = true;
if (pData.isDebugActive(CheckType.MOVING)) { if (pData.isDebugActive(CheckType.MOVING)) {
debug(player, "Set attack slow down flag in this move."); debug(player, "Set attack slow down flag in this move.");
@@ -513,14 +621,4 @@ public class CombinedListener extends CheckListener implements JoinLeaveListener
event.setCancelled(true); event.setCancelled(true);
counters.addPrimaryThread(idFakeInvulnerable, 1); counters.addPrimaryThread(idFakeInvulnerable, 1);
} }
/** We listen for player fishing for the VERY critical and super important munch hausen check */
@EventHandler(priority = EventPriority.LOWEST)
public void onFishing(final PlayerFishEvent event) {
// Check also in case of cancelled events.
final Player player = event.getPlayer();
if (munchHausen.isEnabled(player) && munchHausen.checkFish(player, event.getCaught(), event.getState())) {
event.setCancelled(true);
}
}
} }
@@ -26,51 +26,62 @@ import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.TickTask; import fr.neatmonster.nocheatplus.utilities.TickTask;
/** /**
* This check combines different other checks frequency and occurrences into one count. * Meta-check that aggregates suspicion weights from multiple checks over time,
* (Intended for static access by other checks.) * and evaluates if the overall behavior is statistically unlikely
* ("improbable") for a legitimate player.
* *
* @author asofold * Intended for static access by other checks, which can feed weights into this
* aggregator.
* *
* Buckets are used internally to group suspicion scores into time slices.
*/ */
public class Improbable extends Check implements IDisableListener { public class Improbable extends Check implements IDisableListener {
private static Improbable instance = null; private static Improbable instance = null;
/** /**
* Return if to cancel. * Run the Improbable check for a given player with a new weight.
* @param player *
* @param weights * @param player The player being checked.
* @param now * @param weight The suspicion weight to add (severity of the detected behavior).
* @return * @param now Current system time in ms.
* @param tags Optional tags for additional context.
* @param pData
* @return true if the action should be cancelled, false otherwise.
*/ */
public static final boolean check(final Player player, final float weight, final long now, final String tags, final IPlayerData pData) { public static final boolean check(final Player player, final float weight, final long now, final String tags, final IPlayerData pData) {
return instance.checkImprobable(player, weight, now, tags, pData); return instance.checkImprobable(player, weight, now, tags, pData);
} }
/** /**
* Update time references only, no feeding and no violation. * Advance the internal time window without adding new suspicion.
* @param player * Useful for keeping buckets aligned to the current time without
* @param now * incrementing scores or triggering violations.
* @param pData *
* @param now Current system time in ms.
* @param pData Player data container.
*/ */
public static final void update(final long now, final IPlayerData pData) { public static final void update(final long now, final IPlayerData pData) {
pData.getGenericInstance(CombinedData.class).improbableCount.update(now); pData.getGenericInstance(CombinedData.class).improbableCount.update(now);
} }
/** /**
* Update time references only, no feeding and no violation (convenience method). * Advance the internal time window for a given player without
* @param player * adding new suspicion.
* @param now *
* @param player The player.
* @param now Current system time in ms.
*/ */
public static final void update(final Player player, final long now) { public static final void update(final Player player, final long now) {
update(now, DataManager.getPlayerData(player)); update(now, DataManager.getPlayerData(player));
} }
/** /**
* Feed the check but no violations processing (convenience method). * Add suspicion weight to the player's current bucket without a violation.
* @param player *
* @param weight * @param player The player.
* @param now * @param weight The suspicion weight to add.
* @param now Current system time in ms.
* @param pData * @param pData
*/ */
public static final void feed(final Player player, final float weight, final long now, final IPlayerData pData) { public static final void feed(final Player player, final float weight, final long now, final IPlayerData pData) {
@@ -78,10 +89,11 @@ public class Improbable extends Check implements IDisableListener {
} }
/** /**
* Feed the check but no violations processing (convenience method). * Add suspicion weight to the player's current bucket without a violation.
* @param player *
* @param weight * @param player The player.
* @param now * @param weight The suspicion weight to add.
* @param now Current system time in ms.
*/ */
public static void feed(final Player player, final float weight, long now) { public static void feed(final Player player, final float weight, long now) {
feed(player, weight, now, DataManager.getPlayerData(player)); feed(player, weight, now, DataManager.getPlayerData(player));
@@ -97,14 +109,15 @@ public class Improbable extends Check implements IDisableListener {
} }
/** /**
* Checks a player for improbable behaviour. * Perform the full Improbable check for a player by adding a new weight
* @param player * and evaluating both short-term and long-term suspicion levels.
* @param weight
* @param now
* @param tags
* @param pData
* @return true if to cancel
* *
* @param player The player being checked.
* @param weight The suspicion weight to add.
* @param now Current system time in ms.
* @param tags Optional tags with context about the source of suspicion.
* @param pData
* @return true if the suspicious action should be cancelled, false otherwise.
*/ */
private boolean checkImprobable(final Player player, final float weight, final long now, final String tags, final IPlayerData pData) { private boolean checkImprobable(final Player player, final float weight, final long now, final String tags, final IPlayerData pData) {
if (!pData.isCheckActive(type, player)) { if (!pData.isCheckActive(type, player)) {
@@ -113,12 +126,12 @@ public class Improbable extends Check implements IDisableListener {
final CombinedData data = pData.getGenericInstance(CombinedData.class); final CombinedData data = pData.getGenericInstance(CombinedData.class);
final CombinedConfig cc = pData.getGenericInstance(CombinedConfig.class); final CombinedConfig cc = pData.getGenericInstance(CombinedConfig.class);
data.improbableCount.add(now, weight); data.improbableCount.add(now, weight);
/** Score of the first 3 seconds (a bucket covers 3 secs)*/ // Score of the first bucket (covers ~3 seconds).
final float shortTerm = data.improbableCount.bucketScore(0); final float shortTerm = data.improbableCount.bucketScore(0);
double violation = 0.0; double violation = 0.0;
boolean violated = false; boolean violated = false;
if (shortTerm * 0.8f > cc.improbableLevel / 20.0) { if (shortTerm * 0.8f > cc.improbableLevel / 20.0) {
/** Lag factor for the first window lag (3 seconds) */ // Lag adjustment factor for the short-term window (~3 seconds).
final float lagFactor = pData.getCurrentWorldData().shouldAdjustToLag(type) ? TickTask.getLag(data.improbableCount.bucketDuration(), true) : 1f; final float lagFactor = pData.getCurrentWorldData().shouldAdjustToLag(type) ? TickTask.getLag(data.improbableCount.bucketDuration(), true) : 1f;
// Re-check with lag adaptation. // Re-check with lag adaptation.
if (shortTerm / lagFactor > cc.improbableLevel / 20.0) { if (shortTerm / lagFactor > cc.improbableLevel / 20.0) {
@@ -126,10 +139,10 @@ public class Improbable extends Check implements IDisableListener {
violated = true; violated = true;
} }
} }
/** Sum score of all buckets, no weight */ // Total score across all buckets (~1 minute span).
final double fullTerm = data.improbableCount.score(1.0f); final double fullTerm = data.improbableCount.score(1.0f);
if (fullTerm > cc.improbableLevel) { if (fullTerm > cc.improbableLevel) {
/** Full window lag factor, 1 minute */ // Lag adjustment factor for the full window (~1 minute).
final float lagFactor = pData.getCurrentWorldData().shouldAdjustToLag(type) ? TickTask.getLag(data.improbableCount.bucketDuration() * data.improbableCount.numberOfBuckets(), true) : 1f; final float lagFactor = pData.getCurrentWorldData().shouldAdjustToLag(type) ? TickTask.getLag(data.improbableCount.bucketDuration() * data.improbableCount.numberOfBuckets(), true) : 1f;
// Re-check with lag adaptation. // Re-check with lag adaptation.
if (fullTerm / lagFactor > cc.improbableLevel) { if (fullTerm / lagFactor > cc.improbableLevel) {
@@ -1,52 +0,0 @@
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.combined;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerFishEvent.State;
import fr.neatmonster.nocheatplus.checks.Check;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData;
/**
* Very, very important check.
* TODO: Demote this shit into a simple boolean config option...
* @author asofold
*
*/
public class MunchHausen extends Check {
public MunchHausen(){
super(CheckType.COMBINED_MUNCHHAUSEN);
}
public boolean checkFish(final Player player, final Entity caught, final State state) {
if (caught == null || !(caught instanceof Player)) return false;
final Player caughtPlayer = (Player) caught;
final IPlayerData pData = DataManager.getPlayerData(player);
final CombinedData data = pData.getGenericInstance(CombinedData.class);
if (player.equals(caughtPlayer)){
data.munchHausenVL += 1.0;
if (executeActions(player, data.munchHausenVL, 1.0,
pData.getGenericInstance(CombinedConfig.class).munchHausenActions).willCancel()){
return true;
}
}
else data.munchHausenVL *= 0.96;
return false;
}
}
@@ -108,7 +108,7 @@ public class Critical extends Check {
// 0: A full jump from ground requires more than 6 phases/events. // 0: A full jump from ground requires more than 6 phases/events.
|| mData.sfJumpPhase > 0 && mData.sfJumpPhase <= mData.liftOffEnvelope.getMaxJumpPhase(mData.jumpAmplifier) || mData.sfJumpPhase > 0 && mData.sfJumpPhase <= mData.liftOffEnvelope.getMaxJumpPhase(mData.jumpAmplifier)
&& !moveInfo.from.seekCollisionAbove(0.2) && !moveInfo.from.seekCollisionAbove(0.2)
&& (lastMove.verVelUsed == null || !lastMove.verVelUsed.get(0).hasFlag(VelocityFlags.ORIGIN_BLOCK_BOUNCE)) && (lastMove.verVelUsed.isEmpty() || !lastMove.verVelUsed.get(0).hasFlag(VelocityFlags.ORIGIN_BLOCK_BOUNCE))
// 0: Always invalidate critical hits if we judge the player to be on ground (given enough fall distance) // 0: Always invalidate critical hits if we judge the player to be on ground (given enough fall distance)
|| Math.abs(ncpFallDistance - mcFallDistance) > 1e-5 && (moveInfo.from.isOnGround() || lastMove.touchedGroundWorkaround) || Math.abs(ncpFallDistance - mcFallDistance) > 1e-5 && (moveInfo.from.isOnGround() || lastMove.touchedGroundWorkaround)
// (Let SurvivalFly catch low-jumps). // (Let SurvivalFly catch low-jumps).
@@ -46,12 +46,15 @@ import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace;
import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace.ITraceEntry; import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace.ITraceEntry;
import fr.neatmonster.nocheatplus.checks.moving.player.UnusedVelocity; import fr.neatmonster.nocheatplus.checks.moving.player.UnusedVelocity;
import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags; import fr.neatmonster.nocheatplus.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.compat.*; import fr.neatmonster.nocheatplus.compat.Bridge1_9;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.compat.IBridgeCrossPlugin;
import fr.neatmonster.nocheatplus.compat.bukkit.BridgeEnchant; import fr.neatmonster.nocheatplus.compat.bukkit.BridgeEnchant;
import fr.neatmonster.nocheatplus.compat.bukkit.BridgeHealth; import fr.neatmonster.nocheatplus.compat.bukkit.BridgeHealth;
import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI;
import fr.neatmonster.nocheatplus.components.data.ICheckData; import fr.neatmonster.nocheatplus.components.data.ICheckData;
import fr.neatmonster.nocheatplus.components.data.IData; import fr.neatmonster.nocheatplus.components.data.IData;
import fr.neatmonster.nocheatplus.components.registry.activation.Activation;
import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle; import fr.neatmonster.nocheatplus.components.registry.event.IGenericInstanceHandle;
import fr.neatmonster.nocheatplus.components.registry.factory.IFactoryOne; import fr.neatmonster.nocheatplus.components.registry.factory.IFactoryOne;
import fr.neatmonster.nocheatplus.components.registry.feature.JoinLeaveListener; import fr.neatmonster.nocheatplus.components.registry.feature.JoinLeaveListener;
@@ -62,8 +65,8 @@ import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.IPlayerData; import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.players.PlayerFactoryArgument; import fr.neatmonster.nocheatplus.players.PlayerFactoryArgument;
import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.utilities.entity.InventoryUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask; import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.entity.InventoryUtil;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil; import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.map.BlockProperties; import fr.neatmonster.nocheatplus.utilities.map.BlockProperties;
import fr.neatmonster.nocheatplus.utilities.math.MathUtil; import fr.neatmonster.nocheatplus.utilities.math.MathUtil;
@@ -122,6 +125,8 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
/* Debug */ /* Debug */
private final int idCancelDead = counters.registerKey("cancel.dead"); private final int idCancelDead = counters.registerKey("cancel.dead");
private final boolean protocolLibPresent = new Activation().pluginExist("ProtocolLib").isAvailable();
// Assume it to stay the same all time. // Assume it to stay the same all time.
private final IGenericInstanceHandle<IBridgeCrossPlugin> crossPlugin = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstanceHandle(IBridgeCrossPlugin.class); private final IGenericInstanceHandle<IBridgeCrossPlugin> crossPlugin = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstanceHandle(IBridgeCrossPlugin.class);
@@ -260,10 +265,10 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
// Can't attack with an inventory open. // Can't attack with an inventory open.
if (cc.enforceClosedInventory && !damaged.isDead()) { if (cc.enforceClosedInventory && !damaged.isDead()) {
if (InventoryUtil.hasInventoryOpen(player)) { if (InventoryUtil.hasInventoryOpen(player)) {
// Do not call Open.checkClose here to prevent a disabled open check from deactivating this feature.
player.closeInventory(); player.closeInventory();
// If closed, do reset InventoryData's stored data.
pData.getGenericInstance(InventoryData.class).inventoryOpenTime = 0; pData.getGenericInstance(InventoryData.class).inventoryOpenTime = 0;
Improbable.feed(player, 0.5f, System.currentTimeMillis()); Improbable.feed(player, 1.5f, System.currentTimeMillis());
// (No cancel here! Less invasive for PvP) // (No cancel here! Less invasive for PvP)
} }
} }
@@ -272,7 +277,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (cc.enforceItemRelease && !damaged.isDead()) { if (cc.enforceItemRelease && !damaged.isDead()) {
if (BridgeMisc.isUsingItem(player)) { if (BridgeMisc.isUsingItem(player)) {
pData.requestItemUseResync(); pData.requestItemUseResync();
Improbable.feed(player, 0.5f, System.currentTimeMillis()); Improbable.feed(player, 1.5f, System.currentTimeMillis());
// (No cancel here! Less invasive for PvP) // (No cancel here! Less invasive for PvP)
} }
} }
@@ -661,19 +666,21 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
// Plugin compatibility thing. // Plugin compatibility thing.
damagedData.lastNoDamageTicks = ndt; damagedData.lastNoDamageTicks = ndt;
} }
// Knock-back calculation (1.8: events only fire if they would count by ndt). if (!protocolLibPresent) {
switch (event.getCause()) { // Knock-back calculation (1.8: events only fire if they would count by ndt).
case ENTITY_ATTACK: switch (event.getCause()) {
if (event instanceof EntityDamageByEntityEvent) { case ENTITY_ATTACK:
final Entity entity = ((EntityDamageByEntityEvent) event).getDamager(); if (event instanceof EntityDamageByEntityEvent) {
if ((entity instanceof Player) && !damagedPlayer.isInsideVehicle() final Entity entity = ((EntityDamageByEntityEvent) event).getDamager();
&& damagedPData.getGenericInstance(FightConfig.class).knockBackVelocityPvP) { if ((entity instanceof Player) && !damagedPlayer.isInsideVehicle()
// TODO: Use the velocity event that is sent anyway and replace x/z if 0 (queue max. values). && damagedPData.getGenericInstance(FightConfig.class).knockBackVelocityPvP) {
applyKnockBack((Player) entity, damagedPlayer, damagedData, damagedPData); // TODO: Use the velocity event that is sent anyway and replace x/z if 0 (queue max. values).
applyKnockBack((Player) entity, damagedPlayer, damagedData, damagedPData);
}
} }
} default:
default: break;
break; }
} }
} }
} }
@@ -27,6 +27,7 @@ import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.ViolationData; import fr.neatmonster.nocheatplus.checks.ViolationData;
import fr.neatmonster.nocheatplus.compat.MCAccess; import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.Direction; import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.Direction;
import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.utilities.collision.Axis; import fr.neatmonster.nocheatplus.utilities.collision.Axis;
import fr.neatmonster.nocheatplus.utilities.collision.Axis.RichAxisData; import fr.neatmonster.nocheatplus.utilities.collision.Axis.RichAxisData;
import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils; import fr.neatmonster.nocheatplus.utilities.collision.AxisAlignedBBUtils;
@@ -55,16 +56,25 @@ public class Visible extends Check {
} }
/** /**
* Perform a visibility check to determine whether the player can see the target entity.<br> * Perform a visibility check to determine whether the player can see the target entity.
* The check uses a ray tracing mechanism combined with neighbor block analysis to ascertain visibility, in order to workaround issues with packet inversion and de-synchronization. * This check is look-agnostic: it only uses world geometry and the attacker's eye position; it does not consider yaw/pitch in any way.<br>
* The reason for this is to workaround issues with packet-inversion and de-synchronization.<p>
* The check performs a direct straight ray trace from the target entity (or its block) to the
* attacker's eye. If that ray is unobstructed, the hit is allowed.<br>
* If the direct ray is obstructed, the code attempts to find an alternative path by walking
* through neighbouring block coordinates. The search has been implemented in an opportunistic way: in each iteration, it picks a single neighbour that appears passable, updates the
* ray to originate from that neighbour and proceeds from there, thus, the neighbour walk is NOT exhaustive. There is no backtracking or branch
* enumeration. In fact, a visited set prevents re-checking the same block and {@link fr.neatmonster.nocheatplus.utilities.collision.tracing.ray.RayTracing.getMaxSteps()}
* bounds the search further. Because of these constraints, the algorithm may fail to discover some
* valid alternative paths (false positives), but the limit keeps the check performant.
* *
* @param player The attacking player. * @param player The attacking player.
* @param loc The location of the player's eye position. * @param loc The location of the player's eye position.
* @param damaged The entity being attacked. * @param damaged The entity being attacked.
* @param damagedIsFake Indicates whether the damaged entity is a fake entity. * @param damagedIsFake Indicates whether the damaged entity is a fake entity (NPC).
* @param dLoc The location of the damaged entity. * @param dLoc The location of the damaged entity.
* @param data Fight data. * @param data
* @param cc Fight config. * @param cc
* @return True if the hit is considered invalid due to visibility obstructions, false otherwise. * @return True if the hit is considered invalid due to visibility obstructions, false otherwise.
*/ */
public boolean check(final Player player, final Location loc, public boolean check(final Player player, final Location loc,
@@ -102,6 +112,7 @@ public class Visible extends Check {
final BlockCache blockCache = this.wrapBlockCache.getBlockCache(); final BlockCache blockCache = this.wrapBlockCache.getBlockCache();
blockCache.setAccess(loc.getWorld()); blockCache.setAccess(loc.getWorld());
blockCache.setPlayerData(DataManager.getPlayerData(player));
rayTracing.setBlockCache(blockCache); rayTracing.setBlockCache(blockCache);
// Perform a first ray trace check between the location of the damaged player and attacker's eye position. // Perform a first ray trace check between the location of the damaged player and attacker's eye position.
rayTracing.set(dLoc.getX(), dLoc.getY(), dLoc.getZ(), eyeX, eyeY, eyeZ); rayTracing.set(dLoc.getX(), dLoc.getY(), dLoc.getZ(), eyeX, eyeY, eyeZ);
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff. Show more