diff --git a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/MovingFlying.java b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/MovingFlying.java index 1722fadd..19fb3e5f 100644 --- a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/MovingFlying.java +++ b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/MovingFlying.java @@ -125,6 +125,7 @@ public class MovingFlying extends BaseAdapter { } default: { // Continue. + data.addFlyingQueue(packetData); // TODO: Not the optimal position, perhaps. } } } diff --git a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/ProtocolLibComponent.java b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/ProtocolLibComponent.java index 97a7d8cf..b046749c 100644 --- a/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/ProtocolLibComponent.java +++ b/NCPCompatProtocolLib/src/main/java/fr/neatmonster/nocheatplus/checks/net/protocollib/ProtocolLibComponent.java @@ -182,11 +182,12 @@ public class ProtocolLibComponent implements DisableListener, INotifyReload, Joi } final Player player = event.getPlayer(); final NetConfig cc = configFactory.getConfig(player); + final NetData data = dataFactory.getData(player); if (cc.flyingFrequencyActive) { - final NetData data = dataFactory.getData(player); // Register expected location for comparison with outgoing packets. data.teleportQueue.onTeleportEvent(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); } + data.clearFlyingQueue(); } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/blockinteract/Visible.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/blockinteract/Visible.java index 370ca298..4b24696b 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/blockinteract/Visible.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/blockinteract/Visible.java @@ -10,10 +10,12 @@ import org.bukkit.entity.Player; import org.bukkit.event.block.Action; import org.bukkit.util.Vector; -import fr.neatmonster.nocheatplus.actions.ParameterName; import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; +import fr.neatmonster.nocheatplus.checks.moving.locations.LocUtil; +import fr.neatmonster.nocheatplus.checks.net.NetData; +import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying; import fr.neatmonster.nocheatplus.compat.MCAccess; import fr.neatmonster.nocheatplus.utilities.BlockCache; import fr.neatmonster.nocheatplus.utilities.InteractRayTracing; @@ -31,6 +33,9 @@ public class Visible extends Check { private final List tags = new ArrayList(); + /** For temporary use, no nested use, setWorld(null) after use, etc. */ + private final Location useLoc = new Location(null, 0, 0, 0); + public Visible() { super(CheckType.BLOCKINTERACT_VISIBLE); blockCache = mcAccess.getBlockCache(null); @@ -50,7 +55,7 @@ public class Visible extends Check { public boolean check(final Player player, final Location loc, final Block block, final BlockFace face, final Action action, final BlockInteractData data, final BlockInteractConfig cc) { // TODO: This check might make parts of interact/blockbreak/... + direction (+?) obsolete. // TODO: Might confine what to check for (left/right-click, target blocks depending on item in hand, container blocks). - final boolean collides; + boolean collides; final int blockX = block.getX(); final int blockY = block.getY(); final int blockZ = block.getZ(); @@ -66,11 +71,64 @@ public class Visible extends Check { } else { // Ray-tracing. - final Vector direction = loc.getDirection(); + Vector direction = loc.getDirection(); // Initialize. blockCache.setAccess(loc.getWorld()); rayTracing.setBlockCache(blockCache); collides = checkRayTracing(eyeX, eyeY, eyeZ, direction.getX(), direction.getY(), direction.getZ(), blockX, blockY, blockZ, face, tags, data.debug); + if (collides) { + // Debug output. + if (data.debug) { + debug(player, "pitch=" + loc.getPitch() + " yaw=" + loc.getYaw() + " tags=" + StringUtil.join(tags, "+")); + } + // Re-check with flying packets. + final DataPacketFlying[] flyingQueue = ((NetData) CheckType.NET.getDataFactory().getData(player)).copyFlyingQueue(); + // TODO: Maybe just the latest one does (!). + LocUtil.set(useLoc, loc); + final float oldPitch = useLoc.getPitch(); + final float oldYaw = useLoc.getYaw(); + // TODO: Specific max-recheck-count (likely doesn't equal packet count). + int count = 0; + for (int i = 0; i < flyingQueue.length; i++) { + final DataPacketFlying packetData = flyingQueue[i]; + // TODO: Allow if within threshold(s) of last move. + // TODO: Confine by distance. + // Abort/skipping conditions. + // if (packetData.hasPos) { + // break; + // } + if (!packetData.hasLook) { + continue; + } + // TODO: Might skip last pitch+yaw as well. + if (packetData.pitch == oldPitch && packetData.yaw == oldYaw) { + if (count == 0) { + count = 1; + } + else { + continue; + } + } + else if (count < 4) { + count ++; + } + // Run ray-tracing again with updated pitch and yaw. + useLoc.setPitch(packetData.pitch); + useLoc.setYaw(packetData.yaw); + direction = useLoc.getDirection(); // TODO: Better. + tags.clear(); + tags.add("flying(" + i + ")"); // Interesting if this gets through. + collides = checkRayTracing(eyeX, eyeY, eyeZ, direction.getX(), direction.getY(), direction.getZ(), blockX, blockY, blockZ, face, tags, data.debug); + if (!collides) { + break; + } + // Debug output. + if (data.debug) { + debug(player, "pitch=" + loc.getPitch() + " yaw=" + loc.getYaw() + " tags=" + StringUtil.join(tags, "+")); + } + } + useLoc.setWorld(null); + } // Cleanup. rayTracing.cleanup(); blockCache.cleanup(); @@ -81,20 +139,19 @@ public class Visible extends Check { if (collides) { data.visibleVL += 1; final ViolationData vd = new ViolationData(this, player, data.visibleVL, 1, cc.visibleActions); - if (data.debug || vd.needsParameters()) { - // TODO: Consider adding the start/end/block-type information if debug is set. - vd.setParameter(ParameterName.TAGS, StringUtil.join(tags, "+")); - // Debug output. - if (data.debug) { - debug(player, "pitch=" + loc.getPitch() + " yaw=" + loc.getYaw() ); - } - } + // if (data.debug || vd.needsParameters()) { + // // TODO: Consider adding the start/end/block-type information if debug is set. + // vd.setParameter(ParameterName.TAGS, StringUtil.join(tags, "+")); + // } if (executeActions(vd)) { cancel = true; } } else { data.visibleVL *= 0.99; + if (data.debug) { + debug(player, "pitch=" + loc.getPitch() + " yaw=" + loc.getYaw() + " tags=" + StringUtil.join(tags, "+")); + } } return cancel; @@ -111,11 +168,12 @@ public class Visible extends Check { final int bdZ = blockZ - eyeBlockZ; // Coarse orientation check. - if (bdX != 0 && dirX * bdX <= 0.0 || bdY != 0 && dirY * bdY <= 0.0 || bdZ != 0 && dirZ * bdZ <= 0.0) { - // TODO: There seem to be false positives, do add debug logging with/before violation handling. - tags.add("coarse_orient"); - return true; - } + // TODO: Might skip (axis transitions...)? + // if (bdX != 0 && dirX * bdX <= 0.0 || bdY != 0 && dirY * bdY <= 0.0 || bdZ != 0 && dirZ * bdZ <= 0.0) { + // // TODO: There seem to be false positives, do add debug logging with/before violation handling. + // tags.add("coarse_orient"); + // return true; + // } // TODO: If medium strict, check if the given BlockFace seems acceptable. @@ -128,13 +186,13 @@ public class Visible extends Check { final double tMaxZ = getMaxTime(eyeZ, eyeBlockZ, dirZ, tMinZ); // Point of time of collision. - final double tCollide = Math.max(tMinX, Math.max(tMinY, tMinZ)); + final double tCollide = Math.max(0.0, Math.max(tMinX, Math.max(tMinY, tMinZ))); // Collision location (corrected to be on the clicked block). double collideX = toBlock(eyeX + dirX * tCollide, blockX); double collideY = toBlock(eyeY + dirY * tCollide, blockY); double collideZ = toBlock(eyeZ + dirZ * tCollide, blockZ); - if (!TrigUtil.isSameBlock(blockX, blockY, blockZ, collideX, collideY, collideZ)) { + if (TrigUtil.distanceSquared(0.5 + blockX, 0.5 + blockY, 0.5 + blockZ, collideX, collideY, collideZ) > 0.75) { tags.add("early_block_miss"); } @@ -145,12 +203,15 @@ public class Visible extends Check { // TODO: Option to tolerate a minimal difference in t and use a corrected position then. tags.add("time_miss"); // Bukkit.getServer().broadcastMessage("visible: " + tMinX + "," + tMaxX + " | " + tMinY + "," + tMaxY + " | " + tMinZ + "," + tMaxZ); - return true; + // return true; // TODO: Strict or not (direction check ...). + // Attempt to correct somehow. + collideX = postCorrect(blockX, bdX, collideX); + collideY = postCorrect(blockY, bdY, collideY); + collideZ = postCorrect(blockZ, bdZ, collideZ); } - - // Correct the last-on-block to be on the edge (could be two). + // TODO: Correct towards minimum of all time values, then towards block, rather. if (tMinX == tCollide) { collideX = Math.round(collideX); } @@ -161,7 +222,7 @@ public class Visible extends Check { collideZ = Math.round(collideZ); } - if (!TrigUtil.isSameBlock(blockX, blockY, blockZ, collideX, collideY, collideZ)) { + if (TrigUtil.distanceSquared(0.5 + blockX, 0.5 + blockY, 0.5 + blockZ, collideX, collideY, collideZ) > 0.75) { tags.add("late_block_miss"); } @@ -197,6 +258,24 @@ public class Visible extends Check { return collides; } + /** + * Correct onto the block (from off-block), against the direction. + * + * @param blockC + * @param bdC + * @param collideC + * @return + */ + private double postCorrect(int blockC, int bdC, double collideC) { + int ref = bdC < 0 ? blockC + 1 : blockC; + if (Location.locToBlock(collideC) == ref) { + return collideC; + } + else { + return ref; + } + } + /** * Time until on the block (time = steps of dir). * @param eye diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/net/NetData.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/net/NetData.java index a9934333..a8eca8a3 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/net/NetData.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/net/NetData.java @@ -1,21 +1,29 @@ package fr.neatmonster.nocheatplus.checks.net; +import java.util.LinkedList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.access.ACheckData; +import fr.neatmonster.nocheatplus.checks.net.model.DataPacketFlying; import fr.neatmonster.nocheatplus.checks.net.model.TeleportQueue; import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency; /** - * Data for net checks. Some data structures may not be thread-safe, but - * accessing each checks data individually respecting the sequence of events - * should work. + * Data for net checks. Some data structures may not be thread-safe, intended + * for thread-local use. Order of events should make use within packet handlers + * safe. * * @author asofold * */ public class NetData extends ACheckData { + // Reentrant lock. + private final Lock lock = new ReentrantLock(); + // AttackFrequency public ActionFrequency attackFrequencySeconds = new ActionFrequency(16, 500); @@ -45,8 +53,22 @@ public class NetData extends ACheckData { */ public long lastKeepAliveTime = 0L; + /** + * Detect teleport-ACK packets, consistency check to only use outgoing + * position if there has been a PlayerTeleportEvent for it. + */ public final TeleportQueue teleportQueue = new TeleportQueue(); // TODO: Consider using one lock per data instance and pass here. + /** + * Store past flying packet locations for reference (lock for + * synchronization). Mainly meant for access to flying packets from the + * primary thread. Latest packet is first. + */ + // TODO: Might extend to synchronize with moving events. + private final LinkedList flyingQueue = new LinkedList(); + /** Maximum amount of packets to store. */ + private final int flyingQueueMaxSize = 10; + public NetData(final NetConfig config) { super(config); flyingFrequencyAll = new ActionFrequency(config.flyingFrequencySeconds, 1000L); @@ -55,10 +77,46 @@ public class NetData extends ACheckData { public void onJoin(final Player player) { teleportQueue.clear(); + clearFlyingQueue(); } public void onLeave(Player player) { teleportQueue.clear(); + clearFlyingQueue(); + } + + /** + * Add a packet to the queue. + * + * @param packetData + * @return If a packet has been removed due to exceeding maximum size. + */ + public boolean addFlyingQueue(final DataPacketFlying packetData) { + boolean res = false; + lock.lock(); + flyingQueue.addFirst(packetData); + if (flyingQueue.size() > flyingQueueMaxSize) { + flyingQueue.removeLast(); + res = true; + } + lock.unlock(); + return res; + } + + /** + * Clear the flying packet queue. + */ + public void clearFlyingQueue() { + lock.lock(); + flyingQueue.clear(); + lock.unlock(); + } + + public DataPacketFlying[] copyFlyingQueue() { + lock.lock(); + final DataPacketFlying[] out = flyingQueue.toArray(new DataPacketFlying[flyingQueue.size()]); + lock.unlock(); + return out; } }