Fixes, more tests and more to do for RayTracing.

* More tests for PassableRayTracing (room, rays from outside).
* Alter InteractRayTracing to account for the block interacted with.
* Added tests for InteractRayTracing.

Problems:
* RayTracing may end x-th digit off target, thus in the wrong block.
Suggested fix is to keep correcting t by the absolute coordinates of the
blocks, i.e. calculate the absolute position rather than adding up.
* InteractRayTracing with strict set to false (like in the
blockinteract.visible check) will be too lenient with 1-thick wall
setups and fail test cases.
This commit is contained in:
asofold 2015-03-11 00:03:51 +01:00
parent 8856f68e55
commit a5d6594591
7 changed files with 598 additions and 256 deletions

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -111,7 +111,7 @@ public class MCAccessFactory {
};
// TEMP END //
// 1.7.10
// 1.7.10
try{
return new MCAccessCB3100();
}

View File

@ -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:<br>
* ox<br>
* 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();
}
}

View File

@ -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:<br>
* ox<br>
@ -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();
}
}

View File

@ -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");
}
}
}
}