diff --git a/src/main/java/ac/grim/grimac/checks/impl/scaffolding/LineOfSightPlace.java b/src/main/java/ac/grim/grimac/checks/impl/scaffolding/LineOfSightPlace.java index d2f7254a9e..ceb3b8b9dc 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/scaffolding/LineOfSightPlace.java +++ b/src/main/java/ac/grim/grimac/checks/impl/scaffolding/LineOfSightPlace.java @@ -5,9 +5,10 @@ import ac.grim.grimac.checks.type.BlockPlaceCheck; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.BlockPlace; +import ac.grim.grimac.utils.collisions.HitboxData; +import ac.grim.grimac.utils.collisions.RaycastData; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; -import ac.grim.grimac.utils.data.Pair; -import ac.grim.grimac.utils.math.GrimMath; +import ac.grim.grimac.utils.data.HitData; import ac.grim.grimac.utils.nmsutil.BlockRayTrace; import com.github.retrooper.packetevents.protocol.attribute.Attributes; import com.github.retrooper.packetevents.protocol.player.ClientVersion; @@ -15,9 +16,11 @@ import com.github.retrooper.packetevents.protocol.world.BlockFace; import com.github.retrooper.packetevents.protocol.world.states.type.StateType; import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; -import org.jetbrains.annotations.NotNull; +import com.github.retrooper.packetevents.util.Vector3i; +import org.bukkit.util.Vector; -import java.util.*; +import java.util.HashSet; +import java.util.List; @CheckData(name = "LineOfSightPlace") public class LineOfSightPlace extends BlockPlaceCheck { @@ -170,10 +173,12 @@ private void calculateDirection(double[] result, double xRot, double yRot) { result[2] = xz * player.trigHandler.cos(rotX); } - private boolean getTargetBlock(double[] eyePosition, double[] eyeDirection, double maxDistance, int[] targetBlockVec, BlockFace expectedBlockFace) { - Pair hitData = BlockRayTrace.getNearestReachHitResult(player, eyePosition, eyeDirection, maxDistance, maxDistance, targetBlockVec); + private boolean getTargetBlock(double[] eyePos, double[] eyeDir, double maxDistance, int[] targetBlockVec, BlockFace expectedBlockFace) { + HitData hitData = BlockRayTrace.getNearestReachHitResult(player, eyePos, eyeDir, maxDistance, maxDistance, targetBlockVec, false); + + // we check for hitdata != null because of being in expanded hitbox, or there was no result, do we still need this? - return hitData != null && Arrays.equals(targetBlockVec, hitData.getFirst()) && hitData.getSecond() == expectedBlockFace; + return hitData != null && new Vector3i(targetBlockVec[0], targetBlockVec[1], targetBlockVec[2]).equals(hitData.getPosition()) && hitData.getClosestDirection() == expectedBlockFace; } private boolean isBlockTypeWhitelisted(StateType type) { diff --git a/src/main/java/ac/grim/grimac/utils/collisions/RaycastData.java b/src/main/java/ac/grim/grimac/utils/collisions/RaycastData.java new file mode 100644 index 0000000000..c8ef34fb3b --- /dev/null +++ b/src/main/java/ac/grim/grimac/utils/collisions/RaycastData.java @@ -0,0 +1,98 @@ +package ac.grim.grimac.utils.collisions; + +/* + + */ +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.datatypes.*; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags; +import com.github.retrooper.packetevents.protocol.world.states.type.StateType; +import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +// Expansion to the CollisionData class, which is different than regular ray tracing hitboxes +public enum RaycastData { + + HOPPER((player, item, version, data, x, y, z) -> { + CollisionBox insideShape = new HexCollisionBox(2.0, 11.0, 2.0, 14.0, 16.0, 14.0); + switch (data.getFacing()) { + case NORTH: + new ComplexCollisionBox(insideShape, new HexCollisionBox(6.0, 8.0, 0.0, 10.0, 10.0, 4.0)); + case SOUTH: + new ComplexCollisionBox(insideShape, new HexCollisionBox(6.0, 8.0, 12.0, 10.0, 10.0, 16.0)); + case WEST: + new ComplexCollisionBox(insideShape, new HexCollisionBox(0.0, 8.0, 6.0, 4.0, 10.0, 10.0)); + case EAST: + new ComplexCollisionBox(insideShape, new HexCollisionBox(12.0, 8.0, 6.0, 16.0, 10.0, 10.0)); + default: + return insideShape; + } + }, StateTypes.HOPPER), + + CAULDRON((player, item, version, data, x, y, z) -> { + return new HexCollisionBox(2.0, 4.0, 2.0, 14.0, 16.0, 14.0); + }, BlockTags.CAULDRONS.getStates().toArray(new StateType[0])), + + FULL_BLOCK((player, item, version, data, x, y, z) -> { + return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true); + }, StateTypes.COMPOSTER, StateTypes.SCAFFOLDING); + + + private static final Map lookup = new HashMap<>(); + + static { + for (RaycastData data : RaycastData.values()) { + for (StateType type : data.materials) { + lookup.put(type, data); + } + } + } + + private final StateType[] materials; + private CollisionBox box; + private HitBoxFactory dynamic; + + RaycastData(CollisionBox box, StateType... materials) { + this.box = box; + Set mList = new HashSet<>(Arrays.asList(materials)); + mList.remove(null); // Sets can contain one null + this.materials = mList.toArray(new StateType[0]); + } + + RaycastData(HitBoxFactory dynamic, StateType... materials) { + this.dynamic = dynamic; + Set mList = new HashSet<>(Arrays.asList(materials)); + mList.remove(null); // Sets can contain one null + this.materials = mList.toArray(new StateType[0]); + } + + public static RaycastData getData(StateType material) { + return lookup.get(material); + } + + @Nullable + public static CollisionBox getBlockHitbox(GrimPlayer player, StateType heldItem, ClientVersion version, WrappedBlockState block, int x, int y, int z) { + RaycastData data = getData(block.getType()); + + if (data == null) { + // We explicitly do not want to fallback on HitBoxData or CollisionBox + // Because we only ever use this as a 2nd ray trace + return NoCollisionBox.INSTANCE; + } + + // Simple collision box to override + if (data.box != null) + return data.box.copy().offset(x, y, z); + + // Allow this class to override collision boxes when they aren't the same as regular boxes + HitBoxFactory hitBoxFactory = data.dynamic; + CollisionBox collisionBox = hitBoxFactory.fetch(player, heldItem, version, block, x, y, z); + collisionBox.offset(x, y, z); + return collisionBox; + } +} + diff --git a/src/main/java/ac/grim/grimac/utils/math/GrimMath.java b/src/main/java/ac/grim/grimac/utils/math/GrimMath.java index 4b829e892b..0e05891577 100644 --- a/src/main/java/ac/grim/grimac/utils/math/GrimMath.java +++ b/src/main/java/ac/grim/grimac/utils/math/GrimMath.java @@ -1,5 +1,7 @@ package ac.grim.grimac.utils.math; +import com.github.retrooper.packetevents.protocol.world.BlockFace; +import com.github.retrooper.packetevents.util.Vector3d; import lombok.experimental.UtilityClass; import java.util.List; @@ -111,4 +113,27 @@ public static long hashCode(double x, int y, double z) { l = l * l * 42317861L + l * 11L; return l >> 16; } + + public static BlockFace getFacing(double x, double y, double z) { + return getFacing((float)x, (float)y, (float)z); + } + + public static BlockFace getFacing(float x, float y, float z) { + BlockFace direction = BlockFace.NORTH; + float f = Float.MIN_VALUE; + + for (BlockFace direction2 : BlockFace.values()) { + float g = x * (float)direction2.getModX() + y * (float)direction2.getModY() + z * (float)direction2.getModZ(); + if (g > f) { + f = g; + direction = direction2; + } + } + + return direction; + } + + public static BlockFace getFacing(Vector3d vec) { + return getFacing(vec.x, vec.y, vec.z); + } } diff --git a/src/main/java/ac/grim/grimac/utils/nmsutil/BlockRayTrace.java b/src/main/java/ac/grim/grimac/utils/nmsutil/BlockRayTrace.java index 47d424d6ba..38904f4c8b 100644 --- a/src/main/java/ac/grim/grimac/utils/nmsutil/BlockRayTrace.java +++ b/src/main/java/ac/grim/grimac/utils/nmsutil/BlockRayTrace.java @@ -2,6 +2,7 @@ import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.collisions.HitboxData; +import ac.grim.grimac.utils.collisions.RaycastData; import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; import ac.grim.grimac.utils.collisions.datatypes.NoCollisionBox; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; @@ -172,22 +173,20 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d } @Nullable - public static Pair<@NotNull int[], @NotNull BlockFace> getNearestReachHitResult(GrimPlayer player, double[] eyePos, double[] lookVec, double currentDistance, double maxDistance, int[] targetBlockVec) { + public static HitData getNearestReachHitResult(GrimPlayer player, double[] startPos, double[] lookVec, double currentDistance, double maxDistance, int[] targetBlockVec, boolean raycastContext) { double[] endPos = new double[]{ - eyePos[0] + lookVec[0] * maxDistance, - eyePos[1] + lookVec[1] * maxDistance, - eyePos[2] + lookVec[2] * maxDistance + startPos[0] + lookVec[0] * maxDistance, + startPos[1] + lookVec[1] * maxDistance, + startPos[2] + lookVec[2] * maxDistance }; - double[] currentEnd = new double[]{ - eyePos[0] + lookVec[0] * currentDistance, - eyePos[1] + lookVec[1] * currentDistance, - eyePos[2] + lookVec[2] * currentDistance - }; - - return traverseBlocksLOSP(player, eyePos, endPos, (block, vector3i) -> { - ClientVersion clientVersion = player.getClientVersion(); - CollisionBox data = HitboxData.getBlockHitbox(player, null, clientVersion, block, vector3i[0], vector3i[1], vector3i[2]); + return traverseBlocks(player, startPos, endPos, (block, vector3i) -> { + CollisionBox data; + if (!raycastContext) { + data = HitboxData.getBlockHitbox(player, null, player.getClientVersion(), block, vector3i.x, vector3i.y, vector3i.z); + } else { + data = RaycastData.getBlockHitbox(player, null, player.getClientVersion(), block, vector3i.x, vector3i.y, vector3i.z); + } if (data == NoCollisionBox.INSTANCE) return null; List boxes = new ArrayList<>(); data.downCast(boxes); @@ -198,21 +197,23 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d // BEWARE OF https://bugs.mojang.com/browse/MC-85109 FOR 1.8 PLAYERS // 1.8 Brewing Stand hitbox is a fullblock until it is hit sometimes, can be caused be restarting client and joining server - if (block.getType() == StateTypes.BREWING_STAND && clientVersion.equals(ClientVersion.V_1_8) && Arrays.equals(vector3i, targetBlockVec)) { + if (block.getType() == StateTypes.BREWING_STAND && player.getClientVersion().equals(ClientVersion.V_1_8) && Arrays.equals(new int[]{vector3i.x, vector3i.y, vector3i.z}, targetBlockVec)) { boxes.add(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true)); } - currentEnd[0] = eyePos[0] + lookVec[0] * currentDistance; - currentEnd[1] = eyePos[1] + lookVec[1] * currentDistance; - currentEnd[2] = eyePos[2] + lookVec[2] * currentDistance; + double[] currentEnd = new double[]{ + startPos[0] + lookVec[0] * currentDistance, + startPos[1] + lookVec[1] * currentDistance, + startPos[2] + lookVec[2] * currentDistance + }; for (SimpleCollisionBox box : boxes) { - Pair intercept = ReachUtilsPrimitives.calculateIntercept(box, eyePos, currentEnd); + Pair intercept = ReachUtilsPrimitives.calculateIntercept(box, startPos, currentEnd); if (intercept.getFirst() == null) continue; // No intercept or wrong blockFace double[] hitLoc = intercept.getFirst(); - double distSq = distanceSquared(hitLoc, eyePos); + double distSq = distanceSquared(hitLoc, startPos); if (distSq < bestHitResult) { bestHitResult = distSq; bestHitLoc = hitLoc; @@ -221,7 +222,17 @@ public static HitData traverseBlocks(GrimPlayer player, Vector3d start, Vector3d } if (bestHitLoc != null) { - return new Pair<>(vector3i, bestFace); + HitData hitData = new HitData(vector3i, new Vector(bestHitLoc[0], bestHitLoc[1], bestHitLoc[2]), bestFace, block); +// if (hitData != null) { + if (!raycastContext) { + HitData hitData2 = BlockRayTrace.getNearestReachHitResult(player, startPos, lookVec, maxDistance, maxDistance, targetBlockVec, true); + if (hitData2 != null && hitData2.getBlockHitLocation().subtract(new Vector(startPos[0], startPos[1], startPos[2])).lengthSquared() < hitData.getBlockHitLocation().subtract(new Vector(startPos[0], startPos[1], startPos[2])).lengthSquared()) { +// return new Vector3i(targetBlockVec[0], targetBlockVec[1], targetBlockVec[2]).equals(hitData.getPosition()) && hitData2.getClosestDirection() == expectedBlockFace; + return new HitData(vector3i, hitData.getBlockHitLocation(), hitData2.getClosestDirection(), block); + } + } +// } + return hitData; } return null;