Skip to content

Commit

Permalink
Fix falses on blocks with 'insides/internal walls' (different Raycast…
Browse files Browse the repository at this point in the history
… block placement shape then HitBox shape) like Cauldrons, Hoppers, and Composters
  • Loading branch information
Axionize committed Nov 3, 2024
1 parent 8a690e2 commit b026f49
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
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;
import com.github.retrooper.packetevents.protocol.player.GameMode;
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 {
Expand Down Expand Up @@ -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<int[], BlockFace> 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) {
Expand Down
98 changes: 98 additions & 0 deletions src/main/java/ac/grim/grimac/utils/collisions/RaycastData.java
Original file line number Diff line number Diff line change
@@ -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<StateType, RaycastData> 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<StateType> 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<StateType> 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;
}
}

25 changes: 25 additions & 0 deletions src/main/java/ac/grim/grimac/utils/math/GrimMath.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}
51 changes: 31 additions & 20 deletions src/main/java/ac/grim/grimac/utils/nmsutil/BlockRayTrace.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SimpleCollisionBox> boxes = new ArrayList<>();
data.downCast(boxes);
Expand All @@ -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<double[], BlockFace> intercept = ReachUtilsPrimitives.calculateIntercept(box, eyePos, currentEnd);
Pair<double[], BlockFace> 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;
Expand All @@ -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;
Expand Down

0 comments on commit b026f49

Please sign in to comment.