Skip to content

Commit

Permalink
Work on separating collision boxes vs hitboxes
Browse files Browse the repository at this point in the history
  • Loading branch information
MWHunter committed Nov 13, 2021
1 parent 82a0e0c commit 284897b
Show file tree
Hide file tree
Showing 12 changed files with 696 additions and 46 deletions.
114 changes: 94 additions & 20 deletions src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import ac.grim.grimac.utils.blockplace.BlockPlaceResult;
import ac.grim.grimac.utils.blockstate.BaseBlockState;
import ac.grim.grimac.utils.blockstate.helper.BlockStateHelper;
import ac.grim.grimac.utils.collisions.CollisionData;
import ac.grim.grimac.utils.collisions.HitboxData;
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.HitData;
Expand All @@ -34,6 +34,8 @@
import io.github.retrooper.packetevents.utils.vector.Vector3d;
import io.github.retrooper.packetevents.utils.vector.Vector3i;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;

Expand Down Expand Up @@ -231,28 +233,54 @@ public void onPacketPlayReceive(PacketPlayReceiveEvent event) {
player.packetStateData.receivedSteerVehicle = false;
}

if (PacketType.Play.Client.Util.isBlockPlace(event.getPacketId())) {
// Check for interactable first (door, etc)
// TODO: Buttons and other interactables (they would block the player from placing another block)
if (PacketType.Play.Client.Util.isBlockPlace(event.getPacketId()) && !player.isSneaking) {
WrappedPacketInBlockPlace place = new WrappedPacketInBlockPlace(event.getNMSPacket());
Vector3i blockPosition = place.getBlockPosition();
Direction face = place.getDirection();
// TODO: Support offhand!
ItemStack placedWith = player.bukkitPlayer.getInventory().getItem(player.packetStateData.lastSlotSelected);
Material material = transformMaterial(placedWith);
BlockPlace blockPlace = new BlockPlace(player, blockPosition, face, material);
BlockPlace blockPlace = new BlockPlace(player, blockPosition, null, null);

// Right-clicking a trapdoor/door/etc.
if (Materials.checkFlag(blockPlace.getPlacedAgainstMaterial(), Materials.CLIENT_SIDE_INTERACTABLE)) {
Vector3i location = blockPlace.getPlacedAgainstBlockLocation();
player.compensatedWorld.tickOpenable(location.getX(), location.getY(), location.getZ());
return;
}
}

if (packetID == PacketType.Play.Client.BLOCK_PLACE) {
WrappedPacketInBlockPlace place = new WrappedPacketInBlockPlace(event.getNMSPacket());

// TODO: Support offhand!
ItemStack placedWith = player.bukkitPlayer.getInventory().getItem(player.packetStateData.lastSlotSelected);
Material material = transformMaterial(placedWith);
BlockPlace blockPlace = new BlockPlace(player, null, null, material);

// Lilypads are USE_ITEM (THIS CAN DESYNC, WTF MOJANG)
if (material == XMaterial.LILY_PAD.parseMaterial()) {
placeLilypad(player, blockPlace);
placeLilypad(player, blockPlace); // Pass a block place because lily pads have a hitbox
return;
}

Material toBucketMat = Materials.transformBucketMaterial(material);
if (toBucketMat != null) {
placeWaterLavaSnowBucket(player, blockPlace, toBucketMat);
}

if (material == Material.BUCKET) {
placeBucket(player, blockPlace);
}
}

if (PacketType.Play.Client.Util.isBlockPlace(event.getPacketId())) {
WrappedPacketInBlockPlace place = new WrappedPacketInBlockPlace(event.getNMSPacket());
Vector3i blockPosition = place.getBlockPosition();
Direction face = place.getDirection();
// TODO: Support offhand!
ItemStack placedWith = player.bukkitPlayer.getInventory().getItem(player.packetStateData.lastSlotSelected);
Material material = transformMaterial(placedWith);
BlockPlace blockPlace = new BlockPlace(player, blockPosition, face, material);

if (placedWith != null && material.isBlock()) {
player.checkManager.onBlockPlace(blockPlace);

Expand All @@ -267,26 +295,69 @@ public void onPacketPlayReceive(PacketPlayReceiveEvent event) {
player.checkManager.onPacketReceive(event);
}

private void placeWaterLavaSnowBucket(GrimPlayer player, BlockPlace blockPlace) {
HitData data = getNearestHitResult(player, false);
private void placeWaterLavaSnowBucket(GrimPlayer player, BlockPlace blockPlace, Material toPlace) {
HitData data = getNearestHitResult(player, toPlace, false);
if (data != null) {
blockPlace.setBlockPosition(data.getPosition());
blockPlace.setFace(Direction.valueOf(data.getClosestDirection().name()));

// If we hit a waterloggable block, then the bucket is directly placed
// Otherwise, use the face to determine where to place the bucket
if (Materials.isPlaceableLiquidBucket(blockPlace.getMaterial()) && ServerVersion.getVersion().isNewerThanOrEquals(ServerVersion.v_1_13)) {
BlockData existing = blockPlace.getExistingBlockBlockData();
if (existing instanceof Waterlogged) {
Waterlogged waterlogged = (Waterlogged) existing.clone(); // Don't corrupt palette
waterlogged.setWaterlogged(true);
blockPlace.set(waterlogged);
return;
}
}

// Powder snow, lava, and water all behave like placing normal blocks after checking for waterlogging
blockPlace.set(toPlace);
}
}

private void placeBucket(GrimPlayer player, BlockPlace blockPlace) {
HitData data = getNearestHitResult(player, true);
HitData data = getNearestHitResult(player, null, true);
if (data != null) {
if (data.getState().getMaterial() == Material.POWDER_SNOW) {
blockPlace.set(Material.AIR);
return;
}

}
// We didn't hit fluid
if (player.compensatedWorld.getFluidLevelAt(data.getPosition().getX(), data.getPosition().getY(), data.getPosition().getZ()) == 0)
return;

blockPlace.setBlockPosition(data.getPosition());
blockPlace.setFace(Direction.valueOf(data.getClosestDirection().name()));

private void placeScaffolding(GrimPlayer player, BlockPlace blockPlace) {
HitData data = getNearestHitResult(player, false);
if (ServerVersion.getVersion().isNewerThanOrEquals(ServerVersion.v_1_13)) {
BlockData existing = blockPlace.getExistingBlockBlockData();
if (existing instanceof Waterlogged) {
Waterlogged waterlogged = (Waterlogged) existing.clone(); // Don't corrupt palette
waterlogged.setWaterlogged(false);
blockPlace.set(waterlogged);
return;
}
}

// Therefore, not waterlogged and is a fluid, and is therefore a source block
blockPlace.set(Material.AIR);
}
}

private void placeLilypad(GrimPlayer player, BlockPlace blockPlace) {
HitData data = getNearestHitResult(player, true);
HitData data = getNearestHitResult(player, null, true);
if (data != null) {
// A lilypad cannot replace a fluid
if (player.compensatedWorld.getFluidLevelAt(data.getPosition().getX(), data.getPosition().getY() + 1, data.getPosition().getZ()) > 0)
return;

blockPlace.setBlockPosition(data.getPosition());
blockPlace.setFace(Direction.valueOf(data.getClosestDirection().name()));

// We checked for a full fluid block below here.
if (player.compensatedWorld.getWaterFluidLevelAt(data.getPosition().getX(), data.getPosition().getY(), data.getPosition().getZ()) > 0
|| data.getState().getMaterial() == Material.ICE || data.getState().getMaterial() == Material.FROSTED_ICE) {
Expand All @@ -312,11 +383,12 @@ private Material transformMaterial(ItemStack stack) {
if (stack.getType() == Material.MELON_SEEDS) return Material.MELON_STEM;
if (stack.getType() == Material.WHEAT_SEEDS) return Material.WHEAT;
if (stack.getType() == Material.REDSTONE) return Material.REDSTONE_WIRE;
if (stack.getType() == Material.POWDER_SNOW_BUCKET) return Material.POWDER_SNOW;

return stack.getType();
}

private HitData getNearestHitResult(GrimPlayer player, boolean waterSourcesHaveHitbox) {
private HitData getNearestHitResult(GrimPlayer player, Material heldItem, boolean sourcesHaveHitbox) {
// TODO: When we do this post-tick (fix desync) switch to lastX
Vector3d startingPos = new Vector3d(player.x, player.y + player.getEyeHeight(), player.z);
Vector startingVec = new Vector(startingPos.getX(), startingPos.getY(), startingPos.getZ());
Expand All @@ -325,7 +397,7 @@ private HitData getNearestHitResult(GrimPlayer player, boolean waterSourcesHaveH
Vector3d endPos = new Vector3d(endVec.getX(), endVec.getY(), endVec.getZ());

return traverseBlocks(player, startingPos, endPos, (block, vector3i) -> {
CollisionBox data = CollisionData.getData(block.getMaterial()).getMovementCollisionBox(player, player.getClientVersion(), block, vector3i.getX(), vector3i.getY(), vector3i.getZ());
CollisionBox data = HitboxData.getBlockHitbox(player, heldItem, player.getClientVersion(), block, vector3i.getX(), vector3i.getY(), vector3i.getZ());
List<SimpleCollisionBox> boxes = new ArrayList<>();
data.downCast(boxes);

Expand All @@ -335,15 +407,17 @@ private HitData getNearestHitResult(GrimPlayer player, boolean waterSourcesHaveH
Vector hitLoc = box.intersectsRay(trace, 0, 6);
if (hitLoc != null && hitLoc.distanceSquared(startingVec) < bestHitResult) {
bestHitResult = hitLoc.distanceSquared(startingVec);
bestHitLoc = new Vector(hitLoc.getX() % 1, hitLoc.getY() % 1, hitLoc.getZ() % 1);
bestHitLoc = new Vector(hitLoc.getX() - box.minX, hitLoc.getY() - box.minY, hitLoc.getZ() - box.minZ);
}
}
if (bestHitLoc != null) {
return new HitData(vector3i, bestHitLoc, block);
}

if (waterSourcesHaveHitbox && player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ())) {
double waterHeight = player.compensatedWorld.getWaterFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ());
if (sourcesHaveHitbox &&
(player.compensatedWorld.isWaterSourceBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ())
|| player.compensatedWorld.getLavaFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ()) == (8 / 9f))) {
double waterHeight = player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ());
SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1);
Vector hitLoc = box.intersectsRay(trace, 0, 6);
if (hitLoc != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public void handleChangeBlock(int x, int y, int z, BaseBlockState state) {
isNearFluid = true;
}

if (Materials.checkFlag(state.getMaterial(), Materials.CLIMBABLE) && pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) {
if ((state.getMaterial() == Material.POWDER_SNOW || Materials.checkFlag(state.getMaterial(), Materials.CLIMBABLE)) && pointThreeBox.isIntersected(new SimpleCollisionBox(x, y, z))) {
isNearClimbable = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.github.retrooper.packetevents.utils.player.ClientVersion;
import io.github.retrooper.packetevents.utils.player.Direction;
import io.github.retrooper.packetevents.utils.vector.Vector3i;
import lombok.Setter;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
Expand All @@ -31,7 +32,9 @@

public class BlockPlace {
private static final BlockFace[] BY_2D = new BlockFace[]{BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST};
@Setter
Vector3i blockPosition;
@Setter
Direction face;
private static final Material SOUL_SAND = XMaterial.SOUL_SAND.parseMaterial();
boolean isCancelled = false;
Expand Down Expand Up @@ -464,6 +467,7 @@ public boolean isCancelled() {
return isCancelled;
}

// TODO: "Replaceable" needs to be supported
public Vector3i getPlacedBlockPos() {
int x = blockPosition.getX() + getNormalBlockFace().getX();
int y = blockPosition.getY() + getNormalBlockFace().getY();
Expand Down Expand Up @@ -501,6 +505,7 @@ public void set(BlockFace face, BaseBlockState state) {
set(blockPos, state);
}

// TODO: Check if replaceable
public void set(Vector3i position, BaseBlockState state) {
if (state instanceof FlatBlockState) {
Bukkit.broadcastMessage("Placed " + ((FlatBlockState) state).getBlockData().getAsString(false));
Expand Down
28 changes: 26 additions & 2 deletions src/main/java/ac/grim/grimac/utils/blockdata/WrappedBlockData.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,19 @@ public void getWrappedData(MagicBlockState data) {
}
}, XMaterial.SNOW.parseMaterial()),

AGEABLE(new WrappedAgeable() {
public void getWrappedData(FlatBlockState data) {
Ageable ageable = (Ageable) data.getBlockData();
setAge(ageable.getAge());
}

public void getWrappedData(MagicBlockState data) {
setAge(data.getBlockData());
}
}, XMaterial.BEETROOT.parseMaterial(), XMaterial.CARROT.parseMaterial(), XMaterial.POTATO.parseMaterial(),
XMaterial.WHEAT.parseMaterial(), XMaterial.NETHER_WART.parseMaterial(),
XMaterial.PUMPKIN_STEM.parseMaterial(), XMaterial.MELON_STEM.parseMaterial()),

FRAME(new WrappedFrame() {
public void getWrappedData(FlatBlockState data) {
EndPortalFrame frame = (EndPortalFrame) data.getBlockData();
Expand Down Expand Up @@ -566,7 +579,17 @@ public void getWrappedData(MagicBlockState data) {
}
}, XMaterial.LEVER.parseMaterial()),

TRIPWIRE(new WrappedDirectionalPower() {
TRIPWIRE(new WrappedTripwire() {
public void getWrappedData(FlatBlockState data) {
setAttached(((TripwireHook) data.getBlockData()).isAttached());
}

public void getWrappedData(MagicBlockState data) {
setAttached((data.getBlockData() & 0x4) == 0x4);
}
}, XMaterial.TRIPWIRE.parseMaterial()),

TRIPWIRE_HOOK(new WrappedDirectionalPower() {
public void getWrappedData(FlatBlockState data) {
setDirection(((Directional) data.getBlockData()).getFacing());
setPowered(((Redstone) data.getBlockData()).isPowered());
Expand Down Expand Up @@ -928,7 +951,8 @@ public void getWrappedData(FlatBlockState data) {
XMaterial.POINTED_DRIPSTONE.parseMaterial(), XMaterial.AMETHYST_CLUSTER.parseMaterial(),
XMaterial.POWDER_SNOW.parseMaterial(), XMaterial.SMALL_AMETHYST_BUD.parseMaterial(),
XMaterial.MEDIUM_AMETHYST_BUD.parseMaterial(), XMaterial.LARGE_AMETHYST_BUD.parseMaterial(),
XMaterial.CANDLE.parseMaterial(), XMaterial.LAVA.parseMaterial()), // Lava is only solid on 1.16+
XMaterial.CANDLE.parseMaterial(), XMaterial.LAVA.parseMaterial(),
XMaterial.ATTACHED_MELON_STEM.parseMaterial(), XMaterial.ATTACHED_PUMPKIN_STEM.parseMaterial()), // Lava is only solid on 1.16+


NO_DATA(new WrappedBlockDataValue(), XMaterial.AIR.parseMaterial());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ac.grim.grimac.utils.blockdata.types;

public class WrappedAgeable extends WrappedBlockDataValue {
int age;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ac.grim.grimac.utils.blockdata.types;

public class WrappedTripwire extends WrappedBlockDataValue {
boolean isAttached;

public boolean isAttached() {
return isAttached;
}

public void setAttached(boolean attached) {
isAttached = attached;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,32 @@ public enum BlockPlaceResult {
|| mat.name().contains("SIGN")) // And signs
.toArray(Material[]::new)),


GLOW_LICHEN((player, place) -> {
BlockData lichen = place.getExistingBlockBlockData();
Set<BlockFace> faces = lichen.getMaterial() == Material.GLOW_LICHEN ? ((GlowLichen) lichen).getFaces() : new HashSet<>();

for (BlockFace face : place.getNearestPlacingDirections()) {
// Face already exists.
if (faces.contains(face)) continue;

if (place.isFullFace(face)) {
faces.add(face);
break;
}
}

// Create fresh block data
GlowLichen toSet = (GlowLichen) Material.GLOW_LICHEN.createBlockData();

// Apply the new faces
for (BlockFace face : faces) {
toSet.setFace(face, faces.contains(face));
}

place.set(toSet);
}, XMaterial.GLOW_LICHEN.parseMaterial()),

FACE_ATTACHED_HORIZONTAL_DIRECTIONAL((player, place) -> {
for (BlockFace face : place.getNearestPlacingDirections()) {
if (place.isFullFace(face)) {
Expand All @@ -577,8 +603,7 @@ public enum BlockPlaceResult {
}
}
}, Arrays.stream(Material.values()).filter(mat -> mat.name().contains("BUTTON") // Find all buttons
|| mat.name().contains("LEVER") // And levers
|| mat.name().contains("LICHEN")) // Add lichen too
|| mat.name().contains("LEVER")) // And levers
.toArray(Material[]::new)),

GRINDSTONE((player, place) -> { // Grindstones do not have special survivability requirements
Expand Down
Loading

0 comments on commit 284897b

Please sign in to comment.