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 b31f0575..f09fde7a 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 @@ -15,195 +15,207 @@ import fr.neatmonster.nocheatplus.utilities.BlockProperties; import fr.neatmonster.nocheatplus.utilities.InteractRayTracing; public class Visible extends Check { - - /** Offset from bounds to estimate some reference end position for ray-tracing. */ - private static final double offset = 0.0001; - - private BlockCache blockCache; - - private final InteractRayTracing rayTracing = new InteractRayTracing(false); - public Visible() { - super(CheckType.BLOCKINTERACT_VISIBLE); - blockCache = mcAccess.getBlockCache(null); - rayTracing.setMaxSteps(60); // TODO: Configurable ? - } - - /* (non-Javadoc) - * @see fr.neatmonster.nocheatplus.checks.Check#setMCAccess(fr.neatmonster.nocheatplus.compat.MCAccess) - */ - @Override - public void setMCAccess(MCAccess mcAccess) { - super.setMCAccess(mcAccess); - // Renew the BlockCache instance. - blockCache = mcAccess.getBlockCache(null); - } - - private static final double getEnd(final double[] bounds, final int index, final int mod){ - if (bounds == null){ - return 0.5 + (0.5 + offset) * mod; - } - if (mod == 0){ - // Middle. - return (bounds[index] + bounds[index + 3]) / 2.0; - } - else if (mod == 1){ - // TODO: Slightly outside or dependent on exact position (inside, exact edge, outside)? - return Math.min(1.0, bounds[index + 3]) + offset; - } - else if (mod == -1){ - // TODO: Slightly outside or dependent on exact position (inside, exact edge, outside)? - return Math.max(0.0, bounds[index]) - offset; - } - else{ - throw new IllegalArgumentException("BlockFace.getModX|Y|Z must be 0, 1 or -1."); - } - } + /** Offset from bounds to estimate some reference end position for ray-tracing. */ + private static final double offset = 0.0001; - 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; - final int blockX = block.getX(); - final int blockY = block.getY(); - final int blockZ = block.getZ(); - final double eyeX = loc.getX(); - final double eyeY = loc.getY() + player.getEyeHeight(); - final double eyeZ = loc.getZ(); - - // TODO: Add tags for fail_passable, fail_raytracing, (fail_face). - // TODO: Reachable face check ? - - if (blockX == Location.locToBlock(eyeX) && blockZ == Location.locToBlock(eyeZ) && block.getY() == Location.locToBlock(eyeY)){ - // Player is interacting with the block their head is in. - // TODO: Should the reachable-face-check be done here too (if it is added at all)? - collides = false; - } - else{ - // Initialize. - blockCache.setAccess(loc.getWorld()); - rayTracing.setBlockCache(blockCache); - - collides = checkRayTracing(eyeX, eyeY, eyeZ, blockX, blockY, blockZ, face); - - // Cleanup. - rayTracing.cleanup(); - blockCache.cleanup(); - } - - if (data.debug && player.hasPermission(Permissions.ADMINISTRATION_DEBUG)){ - // TODO: Tags - player.sendMessage("Interact visible: " + (action == Action.RIGHT_CLICK_BLOCK ? "right" : "left") + " collide=" + rayTracing.collides()); + private BlockCache blockCache; + + /** + * Strict set to false, due to false positives. + */ + private final InteractRayTracing rayTracing = new InteractRayTracing(false); + + public Visible() { + super(CheckType.BLOCKINTERACT_VISIBLE); + blockCache = mcAccess.getBlockCache(null); + rayTracing.setMaxSteps(60); // TODO: Configurable ? + } + + /* (non-Javadoc) + * @see fr.neatmonster.nocheatplus.checks.Check#setMCAccess(fr.neatmonster.nocheatplus.compat.MCAccess) + */ + @Override + public void setMCAccess(MCAccess mcAccess) { + super.setMCAccess(mcAccess); + // Renew the BlockCache instance. + blockCache = mcAccess.getBlockCache(null); + } + + private static final double getEnd(final double[] bounds, final int index, final int mod){ + if (bounds == null){ + return 0.5 + (0.5 + offset) * mod; } - - // Actions ? - boolean cancel = false; - if (collides){ - data.visibleVL += 1; - if (executeActions(player, data.visibleVL, 1, cc.visibleActions)){ - cancel = true; - } - } - else{ - data.visibleVL *= 0.99; - } - - return cancel; - } - - private boolean checkRayTracing(final double eyeX, final double eyeY, final double eyeZ, final int blockX, final int blockY, final int blockZ, final BlockFace face){ - - // Estimated target-middle-position (does it for most cases). - @SuppressWarnings("deprecation") - final double[] bounds = BlockProperties.getCorrectedBounds(blockCache, blockX, blockY, blockZ); - final int modX = face.getModX(); - final int modY = face.getModY(); - final int modZ = face.getModZ(); - final double estX = (double) blockX + getEnd(bounds, 0, modX); - final double estY = (double) blockY + getEnd(bounds, 1, modY); - final double estZ = (double) blockZ + getEnd(bounds, 2, modZ); - final int bEstX = Location.locToBlock(estX); - final int bEstY = Location.locToBlock(estY); - final int bEstZ = Location.locToBlock(estZ); - final int estId = blockCache.getTypeId(bEstX, bEstY, bEstZ); - - // Ignore passable if the estimate is on the clicked block. - final boolean skipPassable = blockX == bEstX && blockY == bEstY && blockZ == bEstZ; - - // TODO: Might also use looking direction (test how accurate). - return checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ, estId, bounds, modX, modY, modZ, skipPassable); - - } + if (mod == 0){ + // Middle. + return (bounds[index] + bounds[index + 3]) / 2.0; + } + else if (mod == 1){ + // TODO: Slightly outside or dependent on exact position (inside, exact edge, outside)? + return Math.min(1.0, bounds[index + 3]) + offset; + } + else if (mod == -1){ + // TODO: Slightly outside or dependent on exact position (inside, exact edge, outside)? + return Math.max(0.0, bounds[index]) - offset; + } + else{ + throw new IllegalArgumentException("BlockFace.getModX|Y|Z must be 0, 1 or -1."); + } + } - /** - * Recursively check alternate positions. - * @param eyeX - * @param eyeY - * @param eyeZ - * @param estX - * @param estY - * @param estZ - * @param estId - * @param modX - * @param modY - * @param modZ - * @param skipPassable - * @return - */ - private boolean checkCollision(final double eyeX, final double eyeY, final double eyeZ, final double estX, final double estY, final double estZ, final int estId, final double[] bounds, final int modX, final int modY, final int modZ, final boolean skipPassable) { - // Check current position. - if (skipPassable || BlockProperties.isPassable(blockCache, estX, estY, estZ, estId)){ - // Perform ray-tracing. - rayTracing.set(eyeX, eyeY, eyeZ, estX, estY, estZ); - rayTracing.loop(); - if (!rayTracing.collides() && rayTracing.getStepsDone() < rayTracing.getMaxSteps()){ - return false; - } - } - // Note: Center of bounds is used for mod == 0. - // TODO: Could "sort" positions by setting signum of d by which is closer to the player. - // TODO: Could consider slightly in-set positions. - if (modX == 0){ - // TODO: Might ensure to check if it is the same block? - final double d = bounds == null ? 0.5 : (bounds[3] - bounds[0]) / 2.0; - if (d >= 0.05){ - // Recursion with adapted x position (if differs enough from bounds. - if (!checkCollision(eyeX, eyeY, eyeZ, estX - d, estY, estZ, estId, bounds, 1, modY, modZ, skipPassable)){ - return false; - } - if (!checkCollision(eyeX, eyeY, eyeZ, estX + d, estY, estZ, estId, bounds, 1, modY, modZ, skipPassable)){ - return false; - } - } - } - if (modZ == 0){ - // TODO: Might ensure to check if it is the same block? - final double d = bounds == null ? 0.5 : (bounds[5] - bounds[2]) / 2.0; - if (d >= 0.05){ - // Recursion with adapted x position (if differs enough from bounds. - if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ - d, estId, bounds, 1, modY, 1, skipPassable)){ - return false; - } - if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ + d, estId, bounds, 1, modY, 1, skipPassable)){ - return false; - } - } - } - if (modY == 0){ - // TODO: Might ensure to check if it is the same block? - final double d = bounds == null ? 0.5 : (bounds[4] - bounds[1]) / 2.0; - if (d >= 0.05){ - // Recursion with adapted x position (if differs enough from bounds. - if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY - d, estZ, estId, bounds, 1, 1, 1, skipPassable)){ - return false; - } - if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY + d, estZ, estId, bounds, 1, 1, 1, skipPassable)){ - return false; - } - } - } - - return true; - } + 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; + final int blockX = block.getX(); + final int blockY = block.getY(); + final int blockZ = block.getZ(); + final double eyeX = loc.getX(); + final double eyeY = loc.getY() + player.getEyeHeight(); + final double eyeZ = loc.getZ(); + + // TODO: Add tags for fail_passable, fail_raytracing, (fail_face). + // TODO: Reachable face check ? + + if (blockX == Location.locToBlock(eyeX) && blockZ == Location.locToBlock(eyeZ) && block.getY() == Location.locToBlock(eyeY)){ + // Player is interacting with the block their head is in. + // TODO: Should the reachable-face-check be done here too (if it is added at all)? + collides = false; + } + else{ + // Initialize. + blockCache.setAccess(loc.getWorld()); + rayTracing.setBlockCache(blockCache); + + collides = checkRayTracing(eyeX, eyeY, eyeZ, blockX, blockY, blockZ, face); + + // Cleanup. + rayTracing.cleanup(); + blockCache.cleanup(); + } + + if (data.debug && player.hasPermission(Permissions.ADMINISTRATION_DEBUG)){ + // TODO: Tags + player.sendMessage("Interact visible: " + (action == Action.RIGHT_CLICK_BLOCK ? "right" : "left") + " collide=" + rayTracing.collides()); + } + + // Actions ? + boolean cancel = false; + if (collides){ + data.visibleVL += 1; + if (executeActions(player, data.visibleVL, 1, cc.visibleActions)){ + cancel = true; + } + } + else{ + data.visibleVL *= 0.99; + } + + return cancel; + } + + private boolean checkRayTracing(final double eyeX, final double eyeY, final double eyeZ, final int blockX, final int blockY, final int blockZ, final BlockFace face){ + + /* + * TODO: Always use the exact looking direction first (calculate where + * it hits the target block, and which faces are exposed then, estimate + * alternative positions based on that, get rid of the workaround). + */ + /* + * TODO: Consider using (slightly less than) full block bounds always ? + * (alt: 2,3 classes of size) + */ + // Estimated target-middle-position (does it for most cases). + @SuppressWarnings("deprecation") + final double[] bounds = BlockProperties.getCorrectedBounds(blockCache, blockX, blockY, blockZ); + final int modX = face.getModX(); + final int modY = face.getModY(); + final int modZ = face.getModZ(); + final double estX = (double) blockX + getEnd(bounds, 0, modX); + final double estY = (double) blockY + getEnd(bounds, 1, modY); + final double estZ = (double) blockZ + getEnd(bounds, 2, modZ); + final int bEstX = Location.locToBlock(estX); + final int bEstY = Location.locToBlock(estY); + final int bEstZ = Location.locToBlock(estZ); + final int estId = blockCache.getTypeId(bEstX, bEstY, bEstZ); + + // Ignore passable if the estimate is on the clicked block. + final boolean skipPassable = blockX == bEstX && blockY == bEstY && blockZ == bEstZ; + + // TODO: Might also use looking direction (test how accurate). + return checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ, estId, bounds, modX, modY, modZ, skipPassable, blockX, blockY, blockZ); + + } + + /** + * Recursively check alternate positions. + * @param eyeX + * @param eyeY + * @param eyeZ + * @param estX + * @param estY + * @param estZ + * @param estId + * @param modX + * @param modY + * @param modZ + * @param skipPassable + * @return + */ + private boolean checkCollision(final double eyeX, final double eyeY, final double eyeZ, final double estX, final double estY, final double estZ, final int estId, final double[] bounds, final int modX, final int modY, final int modZ, final boolean skipPassable, final int clickedX, final int clickedY, final int clickedZ) { + // Check current position. + if (skipPassable || BlockProperties.isPassable(blockCache, estX, estY, estZ, estId)){ + // Perform ray-tracing. + rayTracing.set(eyeX, eyeY, eyeZ, estX, estY, estZ, clickedX, clickedY, clickedZ); + rayTracing.loop(); + if (!rayTracing.collides() && rayTracing.getStepsDone() < rayTracing.getMaxSteps()){ + return false; + } + } + // Note: Center of bounds is used for mod == 0. + // TODO: Could "sort" positions by setting signum of d by which is closer to the player. + // TODO: Could consider slightly in-set positions. + if (modX == 0){ + // TODO: Might ensure to check if it is the same block? + final double d = bounds == null ? 0.5 : (bounds[3] - bounds[0]) / 2.0; + if (d >= 0.05){ + // Recursion with adapted x position (if differs enough from bounds. + if (!checkCollision(eyeX, eyeY, eyeZ, estX - d, estY, estZ, estId, bounds, 1, modY, modZ, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + if (!checkCollision(eyeX, eyeY, eyeZ, estX + d, estY, estZ, estId, bounds, 1, modY, modZ, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + } + } + if (modZ == 0){ + // TODO: Might ensure to check if it is the same block? + final double d = bounds == null ? 0.5 : (bounds[5] - bounds[2]) / 2.0; + if (d >= 0.05){ + // Recursion with adapted x position (if differs enough from bounds. + if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ - d, estId, bounds, 1, modY, 1, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY, estZ + d, estId, bounds, 1, modY, 1, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + } + } + if (modY == 0){ + // TODO: Might ensure to check if it is the same block? + final double d = bounds == null ? 0.5 : (bounds[4] - bounds[1]) / 2.0; + if (d >= 0.05){ + // Recursion with adapted x position (if differs enough from bounds. + if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY - d, estZ, estId, bounds, 1, 1, 1, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + if (!checkCollision(eyeX, eyeY, eyeZ, estX, estY + d, estZ, estId, bounds, 1, 1, 1, skipPassable, clickedX, clickedY, clickedZ)){ + return false; + } + } + } + + return true; + } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/FakeBlockCache.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/FakeBlockCache.java index 0079d0f2..d437c410 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/FakeBlockCache.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/FakeBlockCache.java @@ -88,6 +88,52 @@ public class FakeBlockCache extends BlockCache { } } + public void fill(int x1, int y1, int z1, int x2, int y2, int z2, Material type) { + fill(x1, y1, z1, x2, y2, z2, BlockProperties.getId(type), 0, new double[]{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}); + } + + public void fill(int x1, int y1, int z1, int x2, int y2, int z2, int typeId, int data, double[] bounds) { + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y ++) { + for (int z = z1; z <= z2; z++) { + set(x, y, z, typeId, data, bounds); + } + } + } + } + + public void walls(int x1, int y1, int z1, int x2, int y2, int z2, Material type) { + walls(x1, y1, z1, x2, y2, z2, BlockProperties.getId(type), 0, new double[]{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}); + } + + public void walls(int x1, int y1, int z1, int x2, int y2, int z2, int typeId, int data, double[] bounds) { + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y ++) { + for (int z = z1; z <= z2; z++) { + if (x == x1 || x == x2 || z == z1 || z == z2) { + set(x, y, z, typeId, data, bounds); + } + } + } + } + } + + public void room(int x1, int y1, int z1, int x2, int y2, int z2, Material type) { + room(x1, y1, z1, x2, y2, z2, BlockProperties.getId(type), 0, new double[]{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}); + } + + public void room(int x1, int y1, int z1, int x2, int y2, int z2, int typeId, int data, double[] bounds) { + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y ++) { + for (int z = z1; z <= z2; z++) { + if (x == x1 || x == x2 || z == z1 || z == z2 || y == y1 || y == y2) { + set(x, y, z, typeId, data, bounds); + } + } + } + } + } + @Override public void setAccess(World world) { // Ignore. @@ -123,7 +169,7 @@ public class FakeBlockCache extends BlockCache { @Override public boolean standsOnEntity(Entity entity, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { - // TODO: Consider adding blocks where this might be the case. + // TODO: Consider adding cuboids which mean "ground" if the foot location is inside. return false; } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/InteractRayTracing.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/InteractRayTracing.java index 0fa13cf5..26dec161 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/InteractRayTracing.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/InteractRayTracing.java @@ -1,5 +1,8 @@ package fr.neatmonster.nocheatplus.utilities; +import org.bukkit.Location; + + /** * Rough ray-tracing for interaction with something. This does not do any smart end-point guessing. @@ -25,11 +28,13 @@ public class InteractRayTracing extends RayTracing { protected int lastBx, lastBy, lastBz; - public InteractRayTracing(){ + protected int targetX, targetY, targetZ; + + public InteractRayTracing() { super(); } - public InteractRayTracing(boolean strict){ + public InteractRayTracing(boolean strict) { super(); this.strict = strict; } @@ -42,27 +47,43 @@ public class InteractRayTracing extends RayTracing { this.blockCache = blockCache; } - /* (non-Javadoc) - * @see fr.neatmonster.nocheatplus.utilities.RayTracing#set(double, double, double, double, double, double) - */ - @Override public void set(double x0, double y0, double z0, double x1, double y1, double z1) { + set(x0, y0, z0, x1, y1, z1, Location.locToBlock(x1), Location.locToBlock(y1), Location.locToBlock(z1)); + // Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * + * @param x0 + * @param y0 + * @param z0 + * @param x1 + * @param y1 + * @param z1 + * @param targetX The block clicked/interacted with (can be different to the end point of ray-tracing, or ignored with Integer.MAX_VALUE). + * @param targetY + * @param targetZ + */ + public void set(double x0, double y0, double z0, double x1, double y1, double z1, int targetX, int targetY, int targetZ) { super.set(x0, y0, z0, x1, y1, z1); collides = false; lastBx = blockX; lastBy = blockY; lastBz = blockZ; + this.targetX = targetX; + this.targetY = targetY; + this.targetZ = targetZ; } - public boolean collides(){ + public boolean collides() { return collides; } /** * Remove reference to BlockCache. */ - public void cleanup(){ - if (blockCache != null){ + public void cleanup() { + if (blockCache != null) { blockCache = null; } } @@ -74,49 +95,75 @@ public class InteractRayTracing extends RayTracing { * @param blockZ * @return */ - private final boolean doesCollide(int blockX, int blockY, int blockZ){ + private final boolean doesCollide(final int blockX, final int blockY, final int blockZ) { final int id = blockCache.getTypeId(blockX, blockY, blockZ); final long flags = BlockProperties.getBlockFlags(id); - if ((flags & BlockProperties.F_SOLID) == 0){ + if ((flags & BlockProperties.F_SOLID) == 0) { // Ignore non solid blocks anyway. return false; } - if ((flags & (BlockProperties.F_LIQUID | BlockProperties.F_IGN_PASSABLE | BlockProperties.F_STAIRS | BlockProperties.F_VARIABLE)) != 0){ + if ((flags & (BlockProperties.F_LIQUID | BlockProperties.F_IGN_PASSABLE | BlockProperties.F_STAIRS | BlockProperties.F_VARIABLE)) != 0) { // Special cases. // TODO: F_VARIABLE: Bounding boxes are roughly right ? return false; } - if (!blockCache.isFullBounds(blockX, blockY, blockZ)) return false; + if (!blockCache.isFullBounds(blockX, blockY, blockZ)) { + return false; + } return true; } + /** + * Test if the primary line is on the block interacted with (may be a + * different one that the end point of ray-tracing). + * + * @return + */ + public boolean isTargetBlock() { + return targetX != Integer.MAX_VALUE && blockX == targetX && blockY == targetY && blockZ == targetZ; + } + /** * Check if the block may be interacted through by use of some workaround. + * * @param blockX * @param blockY * @param blockZ * @return */ private final boolean allowsWorkaround(final int blockX, final int blockY, final int blockZ) { - // TODO: This allows some bypasses for "strange" setups. + + // TODO: Recode this/other. + + // TODO: This could allow some bypasses for "strange" setups. // TODO: Consider using distance to target as heuristic ? [should not get smaller !?] + // TODO: Consider (min/max) offset for distance. final int dX = blockX - lastBx; final int dY = blockY - lastBy; final int dZ = blockZ - lastBz; final double dSq = dX * dX + dY * dY + dZ * dZ; - for (int i = 0; i < 6; i++){ + // TODO: Limit distance more here !? + for (int i = 0; i < 6; i++) { final int[] dir = incr[i]; final int rX = blockX + dir[0]; - if (Math.abs(lastBx - rX) > 1) continue; + if (Math.abs(lastBx - rX) > 1) { + continue; + } final int rY = blockY + dir[1]; - if (Math.abs(lastBy - rY) > 1) continue; + if (Math.abs(lastBy - rY) > 1) { + continue; + } final int rZ = blockZ + dir[2]; - if (Math.abs(lastBz - rZ) > 1) continue; + if (Math.abs(lastBz - rZ) > 1) { + continue; + } final int dRx = rX - lastBx; final int dRy = rY - lastBy; final int dRz = rZ - lastBz; - if (dRx * dRx + dRy * dRy + dRz * dRz <= dSq) continue; - if (!doesCollide(rX, rY, rZ)){ + if (dRx * dRx + dRy * dRy + dRz * dRz <= dSq) { + continue; + } + if (!doesCollide(rX, rY, rZ)) { // NOTE: Don't check "rX == targetBx && rZ == targetBz && rY == targetBy". return true; } @@ -125,22 +172,24 @@ public class InteractRayTracing extends RayTracing { } @Override - protected boolean step(int blockX, int blockY, int blockZ, double oX, double oY, double oZ, double dT, final boolean isPrimary) { + protected boolean step(final int blockX, final int blockY, final int blockZ, final double oX, final double oY, final double oZ, final double dT, final boolean isPrimary) { // TODO: Make an optional, more precise check (like passable) ? - // TODO: Account for primary line vs. secondary. // TODO: isEndBlock -> blockInteractedWith, because the offset edge might be on the next block. - if (isEndBlock() || !doesCollide(blockX, blockY, blockZ)){ - lastBx = blockX; - lastBy = blockY; - lastBz = blockZ; + // TODO: isTargetBlock checks the primary line (!, might be ok.). + if (isTargetBlock() || !doesCollide(blockX, blockY, blockZ)) { + if (isPrimary) { + lastBx = blockX; + lastBy = blockY; + lastBz = blockZ; + } return true; } - if (strict || blockX == lastBx && blockZ == lastBz && blockY == lastBy){ + if (strict || blockX == lastBx && blockZ == lastBz && blockY == lastBy) { collides = true; return false; } // Check workarounds... - if (allowsWorkaround(blockX, blockY, blockZ)){ + if (isPrimary && allowsWorkaround(blockX, blockY, blockZ)) { lastBx = blockX; lastBy = blockY; lastBz = blockZ; diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/compat/MCAccessFactory.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/compat/MCAccessFactory.java index faa1a9d3..110855ec 100644 --- a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/compat/MCAccessFactory.java +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/compat/MCAccessFactory.java @@ -111,7 +111,7 @@ public class MCAccessFactory { }; // TEMP END // - // 1.7.10 + // 1.7.10 try{ return new MCAccessCB3100(); } diff --git a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestInteractRayTracing.java b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestInteractRayTracing.java new file mode 100644 index 00000000..e83c4eb5 --- /dev/null +++ b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestInteractRayTracing.java @@ -0,0 +1,145 @@ +package fr.neatmonster.nocheatplus.test; + +import org.bukkit.Material; +import org.junit.Test; + +import fr.neatmonster.nocheatplus.logging.StaticLog; +import fr.neatmonster.nocheatplus.utilities.FakeBlockCache; +import fr.neatmonster.nocheatplus.utilities.InteractRayTracing; +import fr.neatmonster.nocheatplus.utilities.build.BuildParameters; + +public class TestInteractRayTracing { + + public final class CenteredInteractRayTracing extends InteractRayTracing { + private int centerX, centerY, centerZ; + public CenteredInteractRayTracing(boolean strict, int centerX, int centerY, int centerZ) { + super(strict); + this.centerX = centerX; + this.centerY = centerY; + this.centerZ = centerZ; + } + @Override + public void set(double x0, double y0, double z0, double x1, double y1, double z1) { + super.set(x0, y0, z0, x1, y1, z1, centerX, centerY, centerZ); + } + } + + // TODO: Blunt copy and paste from TestPassableRayTracing, add something that makes sense. + + public TestInteractRayTracing() { + StaticLog.setUseLogManager(false); + BlockTests.initBlockProperties(); + StaticLog.setUseLogManager(true); + } + + @Test + public void testAir() { + FakeBlockCache bc = new FakeBlockCache(); + InteractRayTracing rt = new InteractRayTracing(); + rt.setBlockCache(bc); + double[] coords = new double[]{0.5, 0.5, -0.5, 0.5, 0.5, 1.5}; + rt.set(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); + rt.loop(); + if (rt.collides()) { + TestRayTracing.doFail("Expect not to collide with air.", coords); + } + if (rt.getStepsDone() > 4) { + TestRayTracing.doFail("Expect less than 4 steps for moving straight through a block of air.", coords); + } + rt.cleanup(); + bc.cleanup(); + } + + /** + * Moving diagonally through an "empty corner", seen from above:
+ * ox
+ * xo + */ + @Test + public void testEmptyCorner() { + FakeBlockCache bc = new FakeBlockCache(); + // The "empty corner" setup. + for (int y = 70 ; y < 73; y ++) { + bc.set(10, y, 10, Material.STONE); + bc.set(11, y, 11, Material.STONE); + } + // Ground. + for (int x = 9; x < 13; x++) { + for (int z = 9; z < 13; z++) { + bc.set(x, 69, z, Material.STONE); + } + } + // TODO: Make work with strict set to false. + InteractRayTracing rt = new InteractRayTracing(true); + //InteractRayTracing rt = new InteractRayTracing(false); + rt.setBlockCache(bc); + // TODO: More Directions, also just behind the corner. + double[][] setups = new double[][] { + // Slightly off the middle (11, y, 11) + {11.4, 70.0, 10.4, 10.6, 70.0, 11.4}, + // Going exactly through the middle (11, y, 11) + {11.4, 70.0, 10.6, 10.6, 70.0, 11.4}, + {11.5, 70.0, 10.5, 10.5, 70.0, 11.5}, + }; + TestRayTracing.runCoordinates(rt, setups, true, false, 3.0, true); + rt.cleanup(); + bc.cleanup(); + } + + @Test + public void testWall() { + FakeBlockCache bc = new FakeBlockCache(); + // Wall using full blocks. + bc.walls(0, 65, 0, 16, 67, 0, Material.STONE); + // Ground using full blocks (roughly 16 margin to each side). + bc.fill(-16, 64, -16, 32, 64, 16, Material.STONE); + // TODO: Test chest like bounds for target blocks. + InteractRayTracing rt = new InteractRayTracing(false); + rt.setBlockCache(bc); + // TODO: More cases, head inside block itself, angles, ... + double[][] noCollision = new double[][] { + {8.5, 66.75, 1.2 , 8.5, 65.8, 1.0}, + {8.5, 66.75, 1.2 , 8.5, 69.0, 0.0}, // "Above enough". + }; + TestRayTracing.runCoordinates(rt, noCollision, false, true, 3.0, true); + double[][] shouldCollide = new double[][] { + {8.5, 66.75, 1.2 , 8.5, 65.8, 0.0}, + {8.5, 66.75, 1.2 , 8.5, 65.8, -0.2}, + }; + TestRayTracing.runCoordinates(rt, shouldCollide, true, false, 3.0, true); + rt.cleanup(); + bc.cleanup(); + } + + @Test + public void testRoom() { + // TODO: Test for differing middle points (negative to positive range, random, selected rays). + FakeBlockCache bc = new FakeBlockCache(); + bc.room(-1, 64, -1, 1, 66, 1, Material.STONE); + // Note that reversed checks are slightly different with the centered version, but start + end blocks are air anyway. + double[] middle = new double[] {0.5, 65.5, 0.5}; // Free spot. + // TODO: Must work with strict set to false. + //CenteredInteractRayTracing rt = new CenteredInteractRayTracing(false, 0, 65, 0); + CenteredInteractRayTracing rt = new CenteredInteractRayTracing(true, 0, 65, 0); + rt.setBlockCache(bc); + double[][] pastFailures = new double[][] { + {2.1393379885667643, 67.18197661625649, 1.7065201483677281 , 0.0, 65.0, 0.0}, + {2.7915547712543676, 66.65545738305906, 1.310222428430474 , 0.0, 65.0, 0.0}, + {0.0, 65.0, 4.5 , 0.0, 65.0, 1.0}, // strict is false. + {-3.5, 61.5, -3.5 , 0.0, 65.0, 0.0} // strict is false. + }; + TestRayTracing.runCoordinates(rt, pastFailures, true, false, 3, true); + boolean intense = BuildParameters.testLevel > 1; + for (double x = -0.5; x < 1.0; x += 0.5) { + for (double y = -0.5; y < 1.0; y += 0.5) { + for (double z = -0.5; z < 1.0; z += 0.5) { + double add = Math.abs(x) + Math.abs(y) + Math.abs(z); + TestRayTracing.runCenterRays(rt, middle[0] + x, middle[1] + y, middle[2] + z, 2.0 + add, intense ? 10000 : 1000, true, false, true); + } + } + } + rt.cleanup(); + bc.cleanup(); + } + +} diff --git a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestPassableRayTracing.java b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestPassableRayTracing.java index 80fa4235..20d4ece3 100644 --- a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestPassableRayTracing.java +++ b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestPassableRayTracing.java @@ -6,22 +6,23 @@ import org.junit.Test; import fr.neatmonster.nocheatplus.logging.StaticLog; import fr.neatmonster.nocheatplus.utilities.FakeBlockCache; import fr.neatmonster.nocheatplus.utilities.PassableRayTracing; +import fr.neatmonster.nocheatplus.utilities.build.BuildParameters; public class TestPassableRayTracing { - + // TODO: Moving into a block, // TODO: Moving out of a block // TODO: Moving horizontally on various kinds of ground (normal, half blocks) // TODO: Moving up stairs etc ? // TODO: From ground and onto ground moves, onto-edge moves (block before edge, into block, etc). // TODO: Randomized tests (Collide with inner sphere, not collide with outer sphere). - + public TestPassableRayTracing() { StaticLog.setUseLogManager(false); BlockTests.initBlockProperties(); StaticLog.setUseLogManager(true); } - + @Test public void testAir() { FakeBlockCache bc = new FakeBlockCache(); @@ -39,7 +40,7 @@ public class TestPassableRayTracing { rt.cleanup(); bc.cleanup(); } - + @Test public void testThroughOneBlock() { FakeBlockCache bc = new FakeBlockCache(); @@ -47,26 +48,26 @@ public class TestPassableRayTracing { PassableRayTracing rt = new PassableRayTracing(); rt.setBlockCache(bc); double[][] setups = new double[][] { - // Through the middle of the block. - {0.5, 0.5, -0.5, 0.5, 0.5, 1.5}, - {-0.5, 0.5, 0.5, 1.5, 0.5, 0.5}, - {0.5, -0.5, 0.5, 0.5, 1.5, 0.5}, - // Along the edges. - {0.5, 0.0, -0.5, 0.5, 0.0, 1.5}, - {-0.5, 0.0, 0.5, 1.5, 0.0, 0.5}, - // Exactly diagonal. - {-0.5, -0.5, -0.5, 1.5, 1.5, 1.5}, // 3d - {-0.5, 0.0, -0.5, 1.5, 0.0, 1.5}, // 2d - // Through a corner. - {1.2, 0.5, 0.5, 0.5, 0.5, 1.2}, - - // TODO: More of each and other... + generic set-ups? + // Through the middle of the block. + {0.5, 0.5, -0.5, 0.5, 0.5, 1.5}, + {-0.5, 0.5, 0.5, 1.5, 0.5, 0.5}, + {0.5, -0.5, 0.5, 0.5, 1.5, 0.5}, + // Along the edges. + {0.5, 0.0, -0.5, 0.5, 0.0, 1.5}, + {-0.5, 0.0, 0.5, 1.5, 0.0, 0.5}, + // Exactly diagonal. + {-0.5, -0.5, -0.5, 1.5, 1.5, 1.5}, // 3d + {-0.5, 0.0, -0.5, 1.5, 0.0, 1.5}, // 2d + // Through a corner. + {1.2, 0.5, 0.5, 0.5, 0.5, 1.2}, + + // TODO: More of each and other... + generic set-ups? }; TestRayTracing.runCoordinates(rt, setups, true, false, 3.0, true); rt.cleanup(); bc.cleanup(); } - + /** * Moving diagonally through an "empty corner", seen from above:
* ox
@@ -88,17 +89,18 @@ public class TestPassableRayTracing { rt.setBlockCache(bc); // TODO: More Directions, over a corner, sides, etc. double[][] setups = new double[][] { - // Slightly off the middle (11, y, 11) - {11.4, 70.0, 10.4, 10.6, 70.0, 11.4}, - // Going exactly through the middle (11, y, 11) - {11.4, 70.0, 10.6, 10.6, 70.0, 11.4}, - {11.5, 70.0, 10.5, 10.5, 70.0, 11.5}, + // Slightly off the middle (11, y, 11) + {11.4, 70.0, 10.4, 10.6, 70.0, 11.4}, + // Going exactly through the middle (11, y, 11) + {11.4, 70.0, 10.6, 10.6, 70.0, 11.4}, + {11.5, 70.0, 10.5, 10.5, 70.0, 11.5}, + //{11.5, 70.0, 10.5, 10.99999999999, 70.0, 11.00000000001}, // TODO: Craft something here }; TestRayTracing.runCoordinates(rt, setups, true, false, 3.0, true); rt.cleanup(); bc.cleanup(); } - + @Test public void testGround() { FakeBlockCache bc = new FakeBlockCache(); @@ -112,21 +114,21 @@ public class TestPassableRayTracing { rt.setBlockCache(bc); // TODO: More Directions, also from air underneath to ground). double[][] noCollision = new double[][] { - {1.3, 66.0, 2.43, 5.25, 66.0, 7.12}, + {1.3, 66.0, 2.43, 5.25, 66.0, 7.12}, }; TestRayTracing.runCoordinates(rt, noCollision, false, true, 3.0, true); double[][] shouldCollide = new double[][] { - {1.3, 65.1, 2.43, 2.3, 65.1, 4.43}, - {1.3, 65.0, 2.43, 2.3, 65.0, 4.43}, - {1.3, 66.0, 2.43, 1.3, 65.9, 2.43}, - {1.3, 66.0, 2.43, 5.25, 65.9, 7.12}, - {1.3, 65.4, 2.43, 1.3, 65.4, 2.43}, // No distance. + {1.3, 65.1, 2.43, 2.3, 65.1, 4.43}, + {1.3, 65.0, 2.43, 2.3, 65.0, 4.43}, + {1.3, 66.0, 2.43, 1.3, 65.9, 2.43}, + {1.3, 66.0, 2.43, 5.25, 65.9, 7.12}, + {1.3, 65.4, 2.43, 1.3, 65.4, 2.43}, // No distance. }; TestRayTracing.runCoordinates(rt, shouldCollide, true, false, 3.0, true); rt.cleanup(); bc.cleanup(); } - + @Test public void testGroundSteps() { FakeBlockCache bc = new FakeBlockCache(); @@ -142,19 +144,39 @@ public class TestPassableRayTracing { rt.setBlockCache(bc); // TODO: More Directions, also from air underneath to ground). double[][] noCollision = new double[][] { - {1.3, 65.5, 2.43, 5.25, 65.5, 7.12}, + {1.3, 65.5, 2.43, 5.25, 65.5, 7.12}, }; TestRayTracing.runCoordinates(rt, noCollision, false, true, 3.0, true); double[][] shouldCollide = new double[][] { - {1.3, 65.1, 2.43, 2.3, 65.1, 7.43}, - {1.3, 65.0, 2.43, 2.3, 65.0, 7.43}, - {1.3, 65.5, 2.43, 1.3, 65.4, 2.43}, - {1.3, 65.5, 2.43, 5.25, 65.4, 7.12}, - {1.3, 65.4, 2.43, 1.3, 65.4, 2.43}, // No distance. + {1.3, 65.1, 2.43, 2.3, 65.1, 7.43}, + {1.3, 65.0, 2.43, 2.3, 65.0, 7.43}, + {1.3, 65.5, 2.43, 1.3, 65.4, 2.43}, + {1.3, 65.5, 2.43, 5.25, 65.4, 7.12}, + {1.3, 65.4, 2.43, 1.3, 65.4, 2.43}, // No distance. }; TestRayTracing.runCoordinates(rt, shouldCollide, true, false, 3.0, true); rt.cleanup(); bc.cleanup(); } - + + @Test + public void testRoom() { + FakeBlockCache bc = new FakeBlockCache(); + bc.room(-1, 64, -1, 1, 66, 1, Material.STONE); + double[] middle = new double[] {0.5, 65.5, 0.5}; // Free spot. + PassableRayTracing rt = new PassableRayTracing(); + rt.setBlockCache(bc); + boolean intense = BuildParameters.testLevel > 1; + for (double x = -0.5; x < 1.0; x += 0.5) { + for (double y = -0.5; y < 1.0; y += 0.5) { + for (double z = -0.5; z < 1.0; z += 0.5) { + double add = Math.abs(x) + Math.abs(y) + Math.abs(z); + TestRayTracing.runCenterRays(rt, middle[0] + x, middle[1] + y, middle[2] + z, 2.0 + add, intense ? 10000 : 1000, true, false, true); + } + } + } + rt.cleanup(); + bc.cleanup(); + } + } diff --git a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestRayTracing.java b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestRayTracing.java index 112a4982..93661c0d 100644 --- a/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestRayTracing.java +++ b/NCPPlugin/src/test/java/fr/neatmonster/nocheatplus/test/TestRayTracing.java @@ -5,6 +5,7 @@ import static org.junit.Assert.fail; import java.util.Random; import org.bukkit.Location; +import org.bukkit.util.Vector; import org.junit.Test; import fr.neatmonster.nocheatplus.utilities.RayTracing; @@ -259,6 +260,16 @@ public class TestRayTracing { // TODO: Add tests for typical coordinates a with interact, passable. } + /** + * + * @param rt + * @param setup + * @param expectCollide + * @param expectNotCollide + * @param stepsManhattan + * @param reverse If set to true, end points will be exchanged for this run (not in addition). + * @param tag + */ public static void runCoordinates(RayTracing rt, double[] setup, boolean expectCollide, boolean expectNotCollide, double stepsManhattan, boolean reverse, String tag) { if (reverse) { rt.set(setup[3], setup [4], setup[5], setup[0], setup[1], setup[2]); @@ -291,7 +302,7 @@ public class TestRayTracing { * @param expectCollide * @param expectNotCollide * @param stepsManhattan - * @param testReversed + * @param testReversed If to test the each ray with reversed end points in addition. */ public static void runCoordinates(RayTracing rt, double[][] setups, boolean expectCollide, boolean expectNotCollide, double stepsManhattan, boolean testReversed) { for (int i = 0; i < setups.length; i++) { @@ -304,4 +315,61 @@ public class TestRayTracing { } } + /** + * Run (some) standard directions towards the center. + * @param rt + * @param cX + * @param cY + * @param cZ + * @param length Rough length of the rays (might be applied per-axis including an additum). + * @param nRandom Test a number of random rays as well. + * @param expectCollide + * @param expectNotCollide + * @param testReversed If to test the each ray with reversed end points in addition. + */ + public static void runCenterRays(RayTracing rt, double cX, double cY, double cZ, double length, int nRandom, boolean expectCollide, boolean expectNotCollide, boolean testReversed) { + double[] mult = new double[] {-1.0, 0.0, 1.0}; + for (int ix = 0; ix < 3; ix ++) { + for (int iy = 0; iy < 3; iy++) { + for (int iz = 0; iz < 3; iz++) { + if (ix == 1 && iy == 1 && iz == 1) { + // Skip the center itself. + continue; + } + double[] coords = new double[] { + cX + length * mult[ix], + cY + length * mult[iy], + cZ + length * mult[iz], + cX, + cY, + cZ + }; + // TODO: Generate differing target points on/near middle as well. + TestRayTracing.runCoordinates(rt, coords, true, false, 3.0, false, ""); + if (testReversed) { + TestRayTracing.runCoordinates(rt, coords, true, false, 3.0, true, ""); + } + } + } + } + // TODO: Consider running block coordinates with larger radius (potentially all within some radius?). + for (int n = 0; n < nRandom; n ++) { + // TODO: Check if normalize is necessary. + // One totally random vector. + Vector vec = Vector.getRandom().normalize().multiply(length); + double[] coords = new double[] { + cX + vec.getX(), + cY + vec.getY(), + cZ + vec.getZ(), + cX, + cY, + cZ + }; + TestRayTracing.runCoordinates(rt, coords, true, false, 3.0, false, "random"); + if (testReversed) { + TestRayTracing.runCoordinates(rt, coords, true, false, 3.0, true, "random"); + } + } + } + }