For clients supporting impulse event-sending, this will only return YES or NO, as input data is always available.
*/ - public AlmostBoolean hasImpulse; + public AlmostBoolean hasImpulse = AlmostBoolean.NO; /** * Indicates the strafing direction (LEFT, RIGHT, or NONE). @@ -279,7 +279,7 @@ public class PlayerMoveData extends MoveData { *This value is set even if horizontal movement could not be accurately predicted, so it may be unreliable, unless the client sends impulse events, in which case it is dependable. * Check {@link PlayerMoveData#hasImpulse} for its reliability prior to version 1.21.2
*/ - public PlayerKeyboardInput.StrafeDirection strafeImpulse; + public PlayerKeyboardInput.StrafeDirection strafeImpulse = PlayerKeyboardInput.StrafeDirection.NONE; /** * Indicates the forward movement direction (FORWARD, BACKWARD, or NONE). @@ -287,7 +287,7 @@ public class PlayerMoveData extends MoveData { *This value is set even if horizontal movement could not be accurately predicted, so it may be unreliable, unless the client sends impulse events, in which case it is dependable. * Check {@link PlayerMoveData#hasImpulse} for its reliability prior to version 1.21.2
*/ - public PlayerKeyboardInput.ForwardDirection forwardImpulse; + public PlayerKeyboardInput.ForwardDirection forwardImpulse = PlayerKeyboardInput.ForwardDirection.NONE; /** * Judge if this horizontal collision ({@link PlayerMoveData#collideX} or {@link PlayerMoveData#collideZ}) is to be considered as minor. diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/HiddenMotionReconstructor.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/HiddenMotionReconstructor.java index 0567590043..bf4e3a2cd8 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/HiddenMotionReconstructor.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/HiddenMotionReconstructor.java @@ -197,8 +197,8 @@ private static double[] reconstructHiddenTicksRecursive(final double yDistance, } // *----------tryCheckInsideBlocks()----------* // Bubble columns are checked in the tryCheckInsideBlocks method, so it comes after updateEntityAfterFallOn()... - //Vector bubbleVector = from.tryApplyBubbleColumnMotion(new Vector(0.0, baseY, 0.0)); - //baseY = bubbleVector.getY(); + Vector bubbleVector = from.tryApplyBubbleColumnMotion(new Vector(0.0, baseY, 0.0)); + baseY = bubbleVector.getY(); // Honey block sliding mechanic... if (from.isSlidingDown()) { // Speed is static in this case @@ -275,7 +275,7 @@ else if (lastMove.from.inLava) { // This condition is the same for both lava and water, and is always done at the end of the travel() function. if (lastMove.from.inLiquid && lastMove.collidesHorizontally // TODO: Somewhat work. Incorrect horizontal move. Require this function call at the time BOTH horizontal and vertical calculating at the same time. Which is not possible with current infrastructure - && from.isUnobstructed(0.0)) { + && from.isUnobstructed()) { baseY = 0.3; } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/MorePackets.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/MorePackets.java index d4e91b099a..94bbd7e9a4 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/MorePackets.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/MorePackets.java @@ -25,10 +25,11 @@ import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; -import fr.neatmonster.nocheatplus.checks.moving.MovingData; -import fr.neatmonster.nocheatplus.checks.net.NetStatic; -import fr.neatmonster.nocheatplus.players.IPlayerData; -import fr.neatmonster.nocheatplus.utilities.StringUtil; +import fr.neatmonster.nocheatplus.checks.moving.MovingData; +import fr.neatmonster.nocheatplus.checks.net.NetStatic; +import fr.neatmonster.nocheatplus.players.IPlayerData; +import fr.neatmonster.nocheatplus.utilities.CheckUtils; +import fr.neatmonster.nocheatplus.utilities.StringUtil; import fr.neatmonster.nocheatplus.utilities.location.PlayerLocation; /** @@ -86,22 +87,28 @@ public Location check(final Player player, final PlayerLocation from, final Play } } - // Check for a violation of the set limits. - tags.clear(); - final double violation = NetStatic.morePacketsCheck(data.morePacketsFreq, time, 1f, cc.morePacketsEPSMax, cc.morePacketsEPSIdeal, data.morePacketsBurstFreq, cc.morePacketsBurstPackets, cc.morePacketsBurstDirect, cc.morePacketsBurstEPM, tags); + // Check for a violation of the set limits. + tags.clear(); + // Diagnostic info: packet-rate tags are separate from SurvivalFly movement-model tags. + final double violation = NetStatic.morePacketsCheck(data.morePacketsFreq, time, 1f, cc.morePacketsEPSMax, cc.morePacketsEPSIdeal, data.morePacketsBurstFreq, cc.morePacketsBurstPackets, cc.morePacketsBurstDirect, cc.morePacketsBurstEPM, tags); // Process violation result. - if (violation > 0.0) { - // Increment violation level. - data.morePacketsVL = violation; // TODO: Accumulate somehow [e.g. always += 1, decrease with continuous moving without violation]? - - // Violation handling. - final ViolationData vd = new ViolationData(this, player, data.morePacketsVL, violation, cc.morePacketsActions); - if (debug || vd.needsParameters()) { - vd.setParameter(ParameterName.PACKETS, Integer.toString(new Double(violation).intValue())); - vd.setParameter(ParameterName.TAGS, StringUtil.join(tags, "+")); - } - if (executeActions(vd).willCancel()) { + if (violation > 0.0) { + // Increment violation level. + data.morePacketsVL = violation; // TODO: Accumulate somehow [e.g. always += 1, decrease with continuous moving without violation]? + // Diagnostic info: expose packet-rate violations as their own subcheck under MOVING_MOREPACKETS. + tags.add(0, "subcheck_morepackets_frequency"); + + // Violation handling. + final ViolationData vd = new ViolationData(this, player, data.morePacketsVL, violation, cc.morePacketsActions); + if (debug || vd.needsParameters()) { + vd.setParameter(ParameterName.PACKETS, Integer.toString(new Double(violation).intValue())); + vd.setParameter(ParameterName.TAGS, StringUtil.join(tags, "+")); + } + if (CheckUtils.shouldLogDebugToConsole()) { + logConsoleDetails(violation, player, from, to, allowSetSetBack, data, cc, pData, time); + } + if (executeActions(vd).willCancel()) { // Set to cancel the move. /* * TODO: Harmonize with MovingUtil.getApplicableSetBackLocation @@ -122,6 +129,92 @@ else if (allowSetSetBack && data.getMorePacketsSetBackAge() > cc.morePacketsSetB // No set back. return null; - } - -} + } + + private void logConsoleDetails(final double violation, final Player player, + final PlayerLocation from, final PlayerLocation to, + final boolean allowSetSetBack, + final MovingData data, final MovingConfig cc, + final IPlayerData pData, final long time) { + try { + // Diagnostic info: console-only packet-rate context for distinguishing packet spam from movement model issues. + final StringBuilder builder = new StringBuilder(620); + builder.append("[NCP][MorePackets][detail] player=").append(player.getName()) + .append(" bedrock=").append(pData.isBedrockPlayer() || player.getName().startsWith(".") + || player.getUniqueId().toString().startsWith("00000000-0000-0000-0009-")) + .append(" bedrockData=").append(pData.isBedrockPlayer()) + .append(" uuid=").append(player.getUniqueId()) + .append(" client=").append(pData.getClientVersion()) + .append(" time=").append(time) + .append(" subcheck=MOREPACKETS_FREQUENCY") + .append(" summary=packet_rate{violation=").append(StringUtil.fdec3.format(violation)) + .append(",epsMax=").append(cc.morePacketsEPSMax) + .append(",burst=").append(cc.morePacketsBurstPackets) + .append(",setback=").append(data.hasMorePacketsSetBack()) + .append('}') + .append(" violation=").append(StringUtil.fdec3.format(violation)) + .append(" totalVL=").append(StringUtil.fdec3.format(data.morePacketsVL)) + .append(" moveCount=").append(data.getPlayerMoveCount()) + .append(" allowSetBack=").append(allowSetSetBack) + .append(" morePacketsSetBack=").append(data.hasMorePacketsSetBack()) + .append(" morePacketsSetBackAge=").append(data.getMorePacketsSetBackAge()) + .append(" from=").append(formatLocation(from)) + .append(" to=").append(formatLocation(to)) + .append(" move=").append(formatMove(from, to)) + .append(" fromEnv=").append(formatEnvironment(from)) + .append(" toEnv=").append(formatEnvironment(to)) + .append(" playerState=sprint:").append(player.isSprinting()) + .append(",sneak:").append(player.isSneaking()) + .append(",fly:").append(player.isFlying()) + .append(",allowFlight:").append(player.getAllowFlight()) + .append(",vehicle:").append(player.isInsideVehicle()) + .append(",velocity=").append(StringUtil.fdec3.format(player.getVelocity().getX())) + .append('/').append(StringUtil.fdec3.format(player.getVelocity().getY())) + .append('/').append(StringUtil.fdec3.format(player.getVelocity().getZ())) + .append(" config=epsIdeal:").append(cc.morePacketsEPSIdeal) + .append(",epsMax:").append(cc.morePacketsEPSMax) + .append(",burstPackets:").append(cc.morePacketsBurstPackets) + .append(",burstDirect:").append(cc.morePacketsBurstDirect) + .append(",burstEPM:").append(cc.morePacketsBurstEPM) + .append(" tags=").append(StringUtil.join(tags, "+")); + player.getServer().getLogger().info(builder.toString()); + } + catch (Throwable t) { + player.getServer().getLogger().info("[NCP][MorePackets][detail] diagnostic logging failed for player=" + + player.getName() + " reason=" + t.getClass().getSimpleName()); + } + } + + private String formatMove(final PlayerLocation from, final PlayerLocation to) { + final double x = to.getX() - from.getX(); + final double y = to.getY() - from.getY(); + final double z = to.getZ() - from.getZ(); + final double h = Math.sqrt(x * x + z * z); + return "x:" + StringUtil.fdec6.format(x) + + ",y:" + StringUtil.fdec6.format(y) + + ",z:" + StringUtil.fdec6.format(z) + + ",h:" + StringUtil.fdec6.format(h); + } + + private String formatLocation(final PlayerLocation location) { + return StringUtil.fdec3.format(location.getX()) + "," + + StringUtil.fdec3.format(location.getY()) + "," + + StringUtil.fdec3.format(location.getZ()); + } + + private String formatEnvironment(final PlayerLocation location) { + return "{block:" + location.getBlockType() + + ",below:" + location.getBlockTypeBelow() + + ",above:" + location.getBlockTypeAbove() + + ",ground:" + location.isOnGround() + + ",reset:" + location.isResetCond() + + ",water:" + location.isInWater() + + ",lava:" + location.isInLava() + + ",waterlogged:" + location.isInWaterLogged() + + ",climb:" + location.isOnClimbable() + + ",web:" + location.isInWeb() + + ",passable:" + location.isPassable() + + "}"; + } + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/NoFall.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/NoFall.java index 36ef30004a..1a75026b52 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/NoFall.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/NoFall.java @@ -429,18 +429,18 @@ public void onTick(int tick, long timeLast) { if (player.getNoDamageTicks() > 0) { return; } - player.setLastDamageCause(event); - mcAccess.getHandle().dealFallDamage(player, BridgeHealth.getRawDamage(event)); - TickTask.removeTickListener(this); - } - }; - TickTask.addTickListener(damagePlayer); - } - else { - player.setLastDamageCause(event); - mcAccess.getHandle().dealFallDamage(player, BridgeHealth.getRawDamage(event)); - } - } + BridgeHealth.setLastDamageCause(player, event); + mcAccess.getHandle().dealFallDamage(player, BridgeHealth.getRawDamage(event)); + TickTask.removeTickListener(this); + } + }; + TickTask.addTickListener(damagePlayer); + } + else { + BridgeHealth.setLastDamageCause(player, event); + mcAccess.getHandle().dealFallDamage(player, BridgeHealth.getRawDamage(event)); + } + } } // Currently resetting is done from within the damage event handler. @@ -629,4 +629,4 @@ public void checkDamage(final Player player, final double y, final MovingData d // Deal damage. handleOnGround(player, y, data.hasSetBack() ? data.getSetBackY() : Double.NEGATIVE_INFINITY, false, data, cc, pData); } -} \ No newline at end of file +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/Passable.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/Passable.java index efdf96eefa..d694255a78 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/Passable.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/Passable.java @@ -26,12 +26,14 @@ import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; -import fr.neatmonster.nocheatplus.checks.moving.MovingData; -import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker; -import fr.neatmonster.nocheatplus.players.IPlayerData; -import fr.neatmonster.nocheatplus.utilities.collision.Axis; -import fr.neatmonster.nocheatplus.utilities.collision.tracing.axis.ICollidePassable; -import fr.neatmonster.nocheatplus.utilities.collision.tracing.axis.PassableAxisTracing; +import fr.neatmonster.nocheatplus.checks.moving.MovingData; +import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker; +import fr.neatmonster.nocheatplus.players.IPlayerData; +import fr.neatmonster.nocheatplus.utilities.CheckUtils; +import fr.neatmonster.nocheatplus.utilities.StringUtil; +import fr.neatmonster.nocheatplus.utilities.collision.Axis; +import fr.neatmonster.nocheatplus.utilities.collision.tracing.axis.ICollidePassable; +import fr.neatmonster.nocheatplus.utilities.collision.tracing.axis.PassableAxisTracing; import fr.neatmonster.nocheatplus.utilities.location.LocUtil; import fr.neatmonster.nocheatplus.utilities.location.PlayerLocation; import fr.neatmonster.nocheatplus.utilities.map.BlockProperties; @@ -87,11 +89,12 @@ private Location checkActual(final Player player, final PlayerLocation from, fin final int manhattan = from.manhattan(to); // Check default order first, then others. - rayTracing.setAxisOrder(Axis.AXIS_ORDER_YXZ); - String newTag = checkRayTracing(player, from, to, manhattan, data, cc, debug, tick, useBlockChangeTracker); - if (newTag != null) { - newTag = checkRayTracingAlernateOrder(player, from, to, manhattan, debug, data, cc, tick, useBlockChangeTracker, newTag); - } + rayTracing.setAxisOrder(Axis.AXIS_ORDER_YXZ); + String newTag = checkRayTracing(player, from, to, manhattan, data, cc, debug, tick, useBlockChangeTracker); + if (newTag != null) { + // Diagnostic info: alternate axis orders identify which collision path caused a passable flag. + newTag = checkRayTracingAlernateOrder(player, from, to, manhattan, debug, data, cc, tick, useBlockChangeTracker, newTag); + } // Finally handle violations. if (newTag == null) { // (Might consider if vl>=1: only decrease if from and loc are passable too, though micro...) @@ -212,20 +215,25 @@ private Location actualViolation(final Player player, } } - // Return the reset position. - data.passableVL += 1d; - final ViolationData vd = new ViolationData(this, player, data.passableVL, 1, cc.passableActions); - if (debug || vd.needsParameters()) { - vd.setParameter(ParameterName.LOCATION_FROM, String.format(Locale.US, "%.2f, %.2f, %.2f", from.getX(), from.getY(), from.getZ())); - vd.setParameter(ParameterName.LOCATION_TO, String.format(Locale.US, "%.2f, %.2f, %.2f", to.getX(), to.getY(), to.getZ())); - vd.setParameter(ParameterName.DISTANCE, String.format(Locale.US, "%.2f", TrigUtil.distance(from, to))); - if (!tags.isEmpty()) { - vd.setParameter(ParameterName.TAGS, tags); - } - } - if (executeActions(vd).willCancel()) { - // TODO: Consider another set back position for this, also keeping track of players moving around in blocks. - final Location newTo; + // Return the reset position. + data.passableVL += 1d; + final ViolationData vd = new ViolationData(this, player, data.passableVL, 1, cc.passableActions); + // Diagnostic info: keep raytrace branch/block context on MOVING_PASSABLE violations. + final String diagnosticTags = getPassableTags(tags, from, to); + if (debug || vd.needsParameters()) { + vd.setParameter(ParameterName.LOCATION_FROM, String.format(Locale.US, "%.2f, %.2f, %.2f", from.getX(), from.getY(), from.getZ())); + vd.setParameter(ParameterName.LOCATION_TO, String.format(Locale.US, "%.2f, %.2f, %.2f", to.getX(), to.getY(), to.getZ())); + vd.setParameter(ParameterName.DISTANCE, String.format(Locale.US, "%.2f", TrigUtil.distance(from, to))); + if (!diagnosticTags.isEmpty()) { + vd.setParameter(ParameterName.TAGS, diagnosticTags); + } + } + if (CheckUtils.shouldLogDebugToConsole()) { + logPassableDetail(player, from, to, data.passableVL, diagnosticTags); + } + if (executeActions(vd).willCancel()) { + // TODO: Consider another set back position for this, also keeping track of players moving around in blocks. + final Location newTo; if (setBackLoc != null) { // Ensure the given location is cloned. newTo = LocUtil.clone(setBackLoc); @@ -241,13 +249,57 @@ private Location actualViolation(final Player player, } else{ // No cancel action set. - return null; - } - } - - /** - * Debug only if colliding. - * + return null; + } + } + + private String getPassableTags(final String tags, final PlayerLocation from, final PlayerLocation to) { + // Diagnostic info: show which passable raytrace branch failed and which blocks were involved. + final String branch = tags == null || tags.isEmpty() ? "unknown" : tags.replaceAll("_+$", ""); + return "subcheck_passable_raytrace" + + "+branch_" + branch.toLowerCase(Locale.ROOT) + + "+from_block_" + from.getBlockType().name().toLowerCase(Locale.ROOT) + + "+to_block_" + to.getBlockType().name().toLowerCase(Locale.ROOT); + } + + private void logPassableDetail(final Player player, final PlayerLocation from, final PlayerLocation to, + final double totalVL, final String tags) { + try { + // Diagnostic info: console-only passable context for stuck-on-block and collision false positives. + player.getServer().getLogger().info(new StringBuilder(420) + .append("[NCP][Passable][detail] player=").append(player.getName()) + .append(" uuid=").append(player.getUniqueId()) + .append(" subcheck=PASSABLE_RAYTRACE") + .append(" summary=passable_raytrace{branch=").append(tags == null ? "unknown" : tags) + .append(",from=").append(from.getBlockType()) + .append(",to=").append(to.getBlockType()) + .append(",vl=").append(StringUtil.fdec3.format(totalVL)) + .append('}') + .append(" totalVL=").append(StringUtil.fdec3.format(totalVL)) + .append(" distance=").append(StringUtil.fdec3.format(TrigUtil.distance(from, to))) + .append(" from=").append(formatLocation(from)) + .append(" to=").append(formatLocation(to)) + .append(" fromBlock=").append(from.getBlockType()) + .append(" toBlock=").append(to.getBlockType()) + .append(" fromPassable=").append(from.isPassable()) + .append(" toPassable=").append(to.isPassable()) + .append(" tags=").append(tags) + .toString()); + } + catch (Throwable ignored) {} + } + + private String formatLocation(final PlayerLocation location) { + return StringUtil.fdec3.format(location.getX()) + + "," + StringUtil.fdec3.format(location.getY()) + + "," + StringUtil.fdec3.format(location.getZ()) + + "/" + StringUtil.fdec3.format(location.getYaw()) + + "," + StringUtil.fdec3.format(location.getPitch()); + } + + /** + * Debug only if colliding. + * * @param player * @param rayTracing * @param tag diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/SurvivalFly.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/SurvivalFly.java index a24af95ef3..982fd85e36 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/SurvivalFly.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/player/SurvivalFly.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Locale; import org.bukkit.ChatColor; @@ -42,12 +43,15 @@ import fr.neatmonster.nocheatplus.checks.moving.model.PlayerKeyboardInput.StrafeDirection; import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope; import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData; +import fr.neatmonster.nocheatplus.checks.moving.velocity.PairEntry; +import fr.neatmonster.nocheatplus.checks.net.NetData; import fr.neatmonster.nocheatplus.checks.workaround.WRPT; import fr.neatmonster.nocheatplus.compat.AlmostBoolean; import fr.neatmonster.nocheatplus.compat.Bridge1_13; import fr.neatmonster.nocheatplus.compat.Bridge1_9; import fr.neatmonster.nocheatplus.compat.BridgeMisc; import fr.neatmonster.nocheatplus.compat.SchedulerHelper; +import fr.neatmonster.nocheatplus.compat.bukkit.BridgeMaterial; import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker; import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeTracker.Direction; import fr.neatmonster.nocheatplus.compat.versions.ClientVersion; @@ -63,6 +67,7 @@ import fr.neatmonster.nocheatplus.utilities.location.PlayerLocation; import fr.neatmonster.nocheatplus.utilities.map.BlockFlags; import fr.neatmonster.nocheatplus.utilities.map.BlockProperties; +import fr.neatmonster.nocheatplus.utilities.map.MaterialUtil; import fr.neatmonster.nocheatplus.utilities.math.MathUtil; import fr.neatmonster.nocheatplus.utilities.math.TrigUtil; import fr.neatmonster.nocheatplus.utilities.moving.Magic; @@ -78,6 +83,405 @@ public class SurvivalFly extends Check { /** To join some tags with moving check violations. */ private final ArrayListOrder of client-movement operations (as per MCP tool): - *
Note: from 1.21.2 and onwards, Mojang split the travel function into different helper methods to better - * distinguish between media (we now have {@code travelInAir()}, {@code travelInFluid()} and {@code travelFallFlying()})
Order of client-movement operations (as per MCP tool): + *
Note: from 1.21.2 and onwards, Mojang split the travel function into different helper methods to better + * distinguish between media (we now have {@code travelInAir()}, {@code travelInFluid()} and {@code travelFallFlying()})