Movement checks (#2547)

This avoids ArrayList allocations and https://github.com/GeyserMC/Geyser/issues/2540.
This commit is contained in:
David Choo 2021-09-26 10:16:22 -04:00 committed by GitHub
parent 6f4d433561
commit 9a8795988f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 199 additions and 198 deletions

View file

@ -27,7 +27,6 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
@ -35,13 +34,12 @@ import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision; import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.registry.BlockRegistries; import org.geysermc.connector.registry.BlockRegistries;
import org.geysermc.connector.utils.BlockPositionIterator;
import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.BlockUtils;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public class FishingHookEntity extends ThrowableEntity { public class FishingHookEntity extends ThrowableEntity {
@ -91,19 +89,16 @@ public class FishingHookEntity extends ThrowableEntity {
boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2); boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2);
boundingBox.setMiddleZ(position.getZ()); boundingBox.setMiddleZ(position.getZ());
CollisionManager collisionManager = session.getCollisionManager();
List<Vector3i> collidableBlocks = collisionManager.getCollidableBlocks(boundingBox);
boolean touchingWater = false; boolean touchingWater = false;
boolean collided = false; boolean collided = false;
for (Vector3i blockPos : collidableBlocks) { for (BlockPositionIterator iter = session.getCollisionManager().collidableBlocksIterator(boundingBox); iter.hasNext(); iter.next()) {
int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos); int blockID = session.getConnector().getWorldManager().getBlockAt(session, iter.getX(), iter.getY(), iter.getZ());
BlockCollision blockCollision = BlockUtils.getCollision(blockID, blockPos); BlockCollision blockCollision = BlockUtils.getCollision(blockID);
if (blockCollision != null) { if (blockCollision != null) {
if (blockCollision.checkIntersection(boundingBox)) { if (blockCollision.checkIntersection(iter.getX(), iter.getY(), iter.getZ(), boundingBox)) {
// TODO Push bounding box out of collision to improve movement // TODO Push bounding box out of collision to improve movement
collided = true; collided = true;
} }
blockCollision.reset();
} }
int waterLevel = BlockStateValues.getWaterLevel(blockID); int waterLevel = BlockStateValues.getWaterLevel(blockID);
@ -111,10 +106,10 @@ public class FishingHookEntity extends ThrowableEntity {
waterLevel = 0; waterLevel = 0;
} }
if (waterLevel >= 0) { if (waterLevel >= 0) {
double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0; double waterMaxY = iter.getY() + 1 - (waterLevel + 1) / 9.0;
// Falling water is a full block // Falling water is a full block
if (waterLevel >= 8) { if (waterLevel >= 8) {
waterMaxY = blockPos.getY() + 1; waterMaxY = iter.getY() + 1;
} }
if (position.getY() <= waterMaxY) { if (position.getY() <= waterMaxY) {
touchingWater = true; touchingWater = true;

View file

@ -73,7 +73,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
// Send book update before the player moves // Send book update before the player moves
session.getBookEditCache().checkForSend(); session.getBookEditCache().checkForSend();
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0)); if (!session.getTeleportMap().isEmpty()) {
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0));
return;
}
// head yaw, pitch, head yaw // head yaw, pitch, head yaw
Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY()); Vector3f rotation = Vector3f.from(packet.getRotation().getY(), packet.getRotation().getX(), packet.getRotation().getY());
@ -96,7 +99,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
return; return;
} }
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) { if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT); Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
if (position != null) { // A null return value cancels the packet if (position != null) { // A null return value cancels the packet
Packet movePacket; Packet movePacket;
@ -155,22 +158,15 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
} }
} }
private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) { private boolean isInvalidNumber(float val) {
if (mode != MovePlayerPacket.Mode.NORMAL) return Float.isNaN(val) || Float.isInfinite(val);
return true; }
double xRange = newPosition.getX() - currentPosition.getX(); private boolean isValidMove(GeyserSession session, Vector3f currentPosition, Vector3f newPosition) {
double yRange = newPosition.getY() - currentPosition.getY(); if (isInvalidNumber(newPosition.getX()) || isInvalidNumber(newPosition.getY()) || isInvalidNumber(newPosition.getZ())) {
double zRange = newPosition.getZ() - currentPosition.getZ(); return false;
}
if (xRange < 0) if (currentPosition.distanceSquared(newPosition) > 300) {
xRange = -xRange;
if (yRange < 0)
yRange = -yRange;
if (zRange < 0)
zRange = -zRange;
if ((xRange + yRange + zRange) > 100) {
session.getConnector().getLogger().debug(ChatColor.RED + session.getName() + " moved too quickly." + session.getConnector().getLogger().debug(ChatColor.RED + session.getName() + " moved too quickly." +
" current position: " + currentPosition + ", new position: " + newPosition); " current position: " + currentPosition + ", new position: " + newPosition);

View file

@ -89,39 +89,41 @@ public class BoundingBox implements Cloneable {
return Vector3d.from(middleX, middleY - sizeY / 2, middleZ); return Vector3d.from(middleX, middleY - sizeY / 2, middleZ);
} }
private boolean checkOverlapInAxis(Vector3d offset, BoundingBox otherBox, Axis axis) { private boolean checkOverlapInAxis(double xOffset, double yOffset, double zOffset, BoundingBox otherBox, Axis axis) {
return switch (axis) { return switch (axis) {
case X -> Math.abs((middleX + offset.getX()) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX()); case X -> Math.abs((middleX + xOffset) - otherBox.getMiddleX()) * 2 < (sizeX + otherBox.getSizeX());
case Y -> Math.abs((middleY + offset.getY()) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY()); case Y -> Math.abs((middleY + yOffset) - otherBox.getMiddleY()) * 2 < (sizeY + otherBox.getSizeY());
case Z -> Math.abs((middleZ + offset.getZ()) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ()); case Z -> Math.abs((middleZ + zOffset) - otherBox.getMiddleZ()) * 2 < (sizeZ + otherBox.getSizeZ());
}; };
} }
/** /**
* Find the maximum offset of another bounding box in an axis that will not collide with this bounding box * Find the maximum offset of another bounding box in an axis that will not collide with this bounding box
* *
* @param boxOffset The offset of this bounding box * @param xOffset The x offset of this bounding box
* @param yOffset The y offset of this bounding box
* @param zOffset The z offset of this bounding box
* @param otherBoundingBox The bounding box that is moving * @param otherBoundingBox The bounding box that is moving
* @param axis The axis of movement * @param axis The axis of movement
* @param offset The current max offset * @param offset The current max offset
* @return The new max offset * @return The new max offset
*/ */
public double getMaxOffset(Vector3d boxOffset, BoundingBox otherBoundingBox, Axis axis, double offset) { public double getMaxOffset(double xOffset, double yOffset, double zOffset, BoundingBox otherBoundingBox, Axis axis, double offset) {
// Make sure that the bounding box overlaps in the other axes // Make sure that the bounding box overlaps in the other axes
for (Axis a : Axis.VALUES) { for (Axis a : Axis.VALUES) {
if (a != axis && !checkOverlapInAxis(boxOffset, otherBoundingBox, a)) { if (a != axis && !checkOverlapInAxis(xOffset, yOffset, zOffset, otherBoundingBox, a)) {
return offset; return offset;
} }
} }
if (offset > 0) { if (offset > 0) {
double min = axis.choose(getMin().add(boxOffset)); double min = axis.choose(getMin().add(xOffset, yOffset, zOffset));
double max = axis.choose(otherBoundingBox.getMax()); double max = axis.choose(otherBoundingBox.getMax());
if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) { if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) {
offset = Math.min(min - max, offset); offset = Math.min(min - max, offset);
} }
} else if (offset < 0) { } else if (offset < 0) {
double min = axis.choose(otherBoundingBox.getMin()); double min = axis.choose(otherBoundingBox.getMin());
double max = axis.choose(getMax().add(boxOffset)); double max = axis.choose(getMax().add(xOffset, yOffset, zOffset));
if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) { if ((min - max) >= -2.0 * CollisionManager.COLLISION_TOLERANCE) {
offset = Math.max(max - min, offset); offset = Math.max(max - min, offset);
} }

View file

@ -42,13 +42,12 @@ import org.geysermc.connector.network.session.cache.PistonCache;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision; import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.collision.translators.ScaffoldingCollision; import org.geysermc.connector.network.translators.collision.translators.ScaffoldingCollision;
import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.utils.BlockPositionIterator;
import org.geysermc.connector.utils.BlockUtils; import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.Axis; import org.geysermc.connector.utils.Axis;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
public class CollisionManager { public class CollisionManager {
@ -133,6 +132,7 @@ public class CollisionManager {
* *
* @param bedrockPosition the current Bedrock position of the client * @param bedrockPosition the current Bedrock position of the client
* @param onGround whether the Bedrock player is on the ground * @param onGround whether the Bedrock player is on the ground
* @param teleported whether the Bedrock player has teleported to a new position. If true, movement correction is skipped.
* @return the position to send to the Java server, or null to cancel sending the packet * @return the position to send to the Java server, or null to cancel sending the packet
*/ */
public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround, boolean teleported) { public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround, boolean teleported) {
@ -201,9 +201,7 @@ public class CollisionManager {
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
} }
public List<Vector3i> getCollidableBlocks(BoundingBox box) { public BlockPositionIterator collidableBlocksIterator(BoundingBox box) {
List<Vector3i> blocks = new ArrayList<>();
Vector3d position = Vector3d.from(box.getMiddleX(), Vector3d position = Vector3d.from(box.getMiddleX(),
box.getMiddleY() - (box.getSizeY() / 2), box.getMiddleY() - (box.getSizeY() / 2),
box.getMiddleZ()); box.getMiddleZ());
@ -211,42 +209,28 @@ public class CollisionManager {
// Expand volume by 1 in each direction to include moving blocks // Expand volume by 1 in each direction to include moving blocks
double pistonExpand = session.getPistonCache().getPistons().isEmpty() ? 0 : 1; double pistonExpand = session.getPistonCache().getPistons().isEmpty() ? 0 : 1;
// Ensure sizes cannot be too large - https://github.com/GeyserMC/Geyser/issues/2540
double sizeX = Math.min(box.getSizeX(), 256);
double sizeY = Math.min(box.getSizeY(), 256);
double sizeZ = Math.min(box.getSizeZ(), 256);
// Loop through all blocks that could collide // Loop through all blocks that could collide
int minCollisionX = (int) Math.floor(position.getX() - ((sizeX / 2) + COLLISION_TOLERANCE + pistonExpand)); int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE + pistonExpand));
int maxCollisionX = (int) Math.floor(position.getX() + (sizeX / 2) + COLLISION_TOLERANCE + pistonExpand); int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE + pistonExpand);
// Y extends 0.5 blocks down because of fence hitboxes // Y extends 0.5 blocks down because of fence hitboxes
int minCollisionY = (int) Math.floor(position.getY() - 0.5 - COLLISION_TOLERANCE - pistonExpand / 2.0); int minCollisionY = (int) Math.floor(position.getY() - 0.5 - COLLISION_TOLERANCE - pistonExpand / 2.0);
int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY() + pistonExpand);
int maxCollisionY = (int) Math.floor(position.getY() + sizeY + pistonExpand); int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand));
int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE + pistonExpand);
int minCollisionZ = (int) Math.floor(position.getZ() - ((sizeZ / 2) + COLLISION_TOLERANCE + pistonExpand)); return new BlockPositionIterator(minCollisionX, minCollisionY, minCollisionZ, maxCollisionX, maxCollisionY, maxCollisionZ);
int maxCollisionZ = (int) Math.floor(position.getZ() + (sizeZ / 2) + COLLISION_TOLERANCE + pistonExpand);
for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
for (int z = minCollisionZ; z < maxCollisionZ + 1; z++) {
blocks.add(Vector3i.from(x, y, z));
}
}
}
return blocks;
} }
public List<Vector3i> getPlayerCollidableBlocks() { public BlockPositionIterator playerCollidableBlocksIterator() {
return getCollidableBlocks(playerBoundingBox); return collidableBlocksIterator(playerBoundingBox);
} }
/** /**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled * cancelled
* See {@link BlockCollision#correctPosition(GeyserSession, BoundingBox)} for more info * See {@link BlockCollision#correctPosition(GeyserSession, int, int, int, BoundingBox)} for more info
*/ */
public boolean correctPlayerPosition() { public boolean correctPlayerPosition() {
@ -254,25 +238,22 @@ public class CollisionManager {
touchingScaffolding = false; touchingScaffolding = false;
onScaffolding = false; onScaffolding = false;
List<Vector3i> collidableBlocks = getPlayerCollidableBlocks();
// Used when correction code needs to be run before the main correction // Used when correction code needs to be run before the main correction
for (Vector3i blockPos : collidableBlocks) { BlockPositionIterator iter = session.getCollisionManager().playerCollidableBlocksIterator();
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, blockPos); for (; iter.hasNext(); iter.next()) {
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ());
if (blockCollision != null) { if (blockCollision != null) {
blockCollision.beforeCorrectPosition(playerBoundingBox); blockCollision.beforeCorrectPosition(iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox);
blockCollision.reset();
} }
} }
// Main correction code // Main correction code
for (Vector3i blockPos : collidableBlocks) { for (iter.reset(); iter.hasNext(); iter.next()) {
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, blockPos); BlockCollision blockCollision = BlockUtils.getCollisionAt(session, iter.getX(), iter.getY(), iter.getZ());
if (blockCollision != null) { if (blockCollision != null) {
if (!blockCollision.correctPosition(session, playerBoundingBox)) { if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) {
return false; return false;
} }
blockCollision.reset();
} }
} }
@ -341,24 +322,22 @@ public class CollisionManager {
BoundingBox movementBoundingBox = boundingBox.clone(); BoundingBox movementBoundingBox = boundingBox.clone();
movementBoundingBox.extend(movement); movementBoundingBox.extend(movement);
BlockPositionIterator iter = collidableBlocksIterator(movementBoundingBox);
List<Vector3i> collidableBlocks = getCollidableBlocks(movementBoundingBox);
if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) { if (Math.abs(movementY) > CollisionManager.COLLISION_TOLERANCE) {
movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, collidableBlocks, checkWorld); movementY = computeCollisionOffset(boundingBox, Axis.Y, movementY, iter, checkWorld);
boundingBox.translate(0, movementY, 0); boundingBox.translate(0, movementY, 0);
} }
boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX); boolean checkZFirst = Math.abs(movementZ) > Math.abs(movementX);
if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { if (checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, collidableBlocks, checkWorld); movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
boundingBox.translate(0, 0, movementZ); boundingBox.translate(0, 0, movementZ);
} }
if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) { if (Math.abs(movementX) > CollisionManager.COLLISION_TOLERANCE) {
movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, collidableBlocks, checkWorld); movementX = computeCollisionOffset(boundingBox, Axis.X, movementX, iter, checkWorld);
boundingBox.translate(movementX, 0, 0); boundingBox.translate(movementX, 0, 0);
} }
if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) { if (!checkZFirst && Math.abs(movementZ) > CollisionManager.COLLISION_TOLERANCE) {
movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, collidableBlocks, checkWorld); movementZ = computeCollisionOffset(boundingBox, Axis.Z, movementZ, iter, checkWorld);
boundingBox.translate(0, 0, movementZ); boundingBox.translate(0, 0, movementZ);
} }
@ -366,16 +345,18 @@ public class CollisionManager {
return Vector3d.from(movementX, movementY, movementZ); return Vector3d.from(movementX, movementY, movementZ);
} }
private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, List<Vector3i> collidableBlocks, boolean checkWorld) { private double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset, BlockPositionIterator iter, boolean checkWorld) {
for (Vector3i blockPos : collidableBlocks) { for (iter.reset(); iter.hasNext(); iter.next()) {
int x = iter.getX();
int y = iter.getY();
int z = iter.getZ();
if (checkWorld) { if (checkWorld) {
BlockCollision blockCollision = BlockUtils.getCollisionAt(session, blockPos); BlockCollision blockCollision = BlockUtils.getCollisionAt(session, x, y, z);
if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) { if (blockCollision != null && !(blockCollision instanceof ScaffoldingCollision)) {
offset = blockCollision.computeCollisionOffset(boundingBox, axis, offset); offset = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, offset);
blockCollision.reset();
} }
} }
offset = session.getPistonCache().computeCollisionOffset(blockPos, boundingBox, axis, offset); offset = session.getPistonCache().computeCollisionOffset(Vector3i.from(x, y, z), boundingBox, axis, offset);
if (Math.abs(offset) < COLLISION_TOLERANCE) { if (Math.abs(offset) < COLLISION_TOLERANCE) {
return 0; return 0;
} }
@ -399,9 +380,8 @@ public class CollisionManager {
playerBoundingBox.setSizeY(EntityType.PLAYER.getHeight()); playerBoundingBox.setSizeY(EntityType.PLAYER.getHeight());
playerBoundingBox.setMiddleY(standingY); playerBoundingBox.setMiddleY(standingY);
boolean result = collision.checkIntersection(playerBoundingBox); boolean result = collision.checkIntersection(position, playerBoundingBox);
result |= session.getPistonCache().checkCollision(position, playerBoundingBox); result |= session.getPistonCache().checkCollision(position, playerBoundingBox);
collision.reset();
playerBoundingBox.setSizeY(originalHeight); playerBoundingBox.setSizeY(originalHeight);
playerBoundingBox.setMiddleY(originalY); playerBoundingBox.setMiddleY(originalY);
return result; return result;

View file

@ -40,16 +40,6 @@ public class BlockCollision {
@Getter @Getter
protected final BoundingBox[] boundingBoxes; protected final BoundingBox[] boundingBoxes;
@EqualsAndHashCode.Exclude
protected final ThreadLocal<Vector3i> position;
/**
* Store a Vector3d to allow the collision to be offset by a fractional amount
* This is used only in {@link #checkIntersection(BoundingBox)} and {@link #computeCollisionOffset(BoundingBox, Axis, double)}
*/
@EqualsAndHashCode.Exclude
protected final ThreadLocal<Vector3d> positionOffset;
/** /**
* This is used for the step up logic. * This is used for the step up logic.
* Usually, the player can only step up a block if they are on the same Y level as its bottom face or higher * Usually, the player can only step up a block if they are on the same Y level as its bottom face or higher
@ -68,28 +58,13 @@ public class BlockCollision {
protected BlockCollision(BoundingBox[] boxes) { protected BlockCollision(BoundingBox[] boxes) {
this.boundingBoxes = boxes; this.boundingBoxes = boxes;
this.position = new ThreadLocal<>();
this.positionOffset = new ThreadLocal<>();
}
public void setPosition(Vector3i newPosition) {
this.position.set(newPosition);
}
public void setPositionOffset(Vector3d newOffset) {
this.positionOffset.set(newOffset);
}
public void reset() {
this.position.set(null);
this.positionOffset.set(null);
} }
/** /**
* Overridden in classes like SnowCollision and GrassPathCollision when correction code needs to be run before the * Overridden in classes like SnowCollision and GrassPathCollision when correction code needs to be run before the
* main correction * main correction
*/ */
public void beforeCorrectPosition(BoundingBox playerCollision) {} public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) {}
/** /**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
@ -97,12 +72,7 @@ public class BlockCollision {
* While the Java server should do this, it could result in false flags by anticheat * While the Java server should do this, it could result in false flags by anticheat
* This functionality is currently only used in 6 or 7 layer snow * This functionality is currently only used in 6 or 7 layer snow
*/ */
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) {
Vector3i blockPos = this.position.get();
int x = blockPos.getX();
int y = blockPos.getY();
int z = blockPos.getZ();
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
for (BoundingBox b : this.boundingBoxes) { for (BoundingBox b : this.boundingBoxes) {
double boxMinY = (b.getMiddleY() + y) - (b.getSizeY() / 2); double boxMinY = (b.getMiddleY() + y) - (b.getSizeY() / 2);
@ -174,29 +144,22 @@ public class BlockCollision {
return true; return true;
} }
private Vector3d getFullPos() { public boolean checkIntersection(double x, double y, double z, BoundingBox playerCollision) {
Vector3i blockPos = this.position.get();
Vector3d blockOffset = this.positionOffset.get();
if (blockOffset != null && blockOffset != Vector3d.ZERO) {
return blockOffset.add(blockPos.getX(), blockPos.getY(), blockPos.getZ());
}
return blockPos.toDouble();
}
public boolean checkIntersection(BoundingBox playerCollision) {
Vector3d blockPos = getFullPos();
for (BoundingBox b : boundingBoxes) { for (BoundingBox b : boundingBoxes) {
if (b.checkIntersection(blockPos, playerCollision)) { if (b.checkIntersection(x, y, z, playerCollision)) {
return true; return true;
} }
} }
return false; return false;
} }
public double computeCollisionOffset(BoundingBox boundingBox, Axis axis, double offset) { public boolean checkIntersection(Vector3i position, BoundingBox playerCollision) {
Vector3d blockPos = getFullPos(); return checkIntersection(position.getX(), position.getY(), position.getZ(), playerCollision);
}
public double computeCollisionOffset(double x, double y, double z, BoundingBox boundingBox, Axis axis, double offset) {
for (BoundingBox b : boundingBoxes) { for (BoundingBox b : boundingBoxes) {
offset = b.getMaxOffset(blockPos, boundingBox, axis, offset); offset = b.getMaxOffset(x, y, z, boundingBox, axis, offset);
if (Math.abs(offset) < CollisionManager.COLLISION_TOLERANCE) { if (Math.abs(offset) < CollisionManager.COLLISION_TOLERANCE) {
return 0; return 0;
} }

View file

@ -40,10 +40,10 @@ public class DirtPathCollision extends BlockCollision {
// Needs to run before the main correction code or it can move the player into blocks // Needs to run before the main correction code or it can move the player into blocks
// This is counteracted by the main collision code pushing them out // This is counteracted by the main collision code pushing them out
@Override @Override
public void beforeCorrectPosition(BoundingBox playerCollision) { public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) {
// In Bedrock, dirt paths are solid blocks, so the player must be pushed down. // In Bedrock, dirt paths are solid blocks, so the player must be pushed down.
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
double blockMaxY = position.get().getY() + 1; double blockMaxY = y + 1;
if (Math.abs(blockMaxY - playerMinY) <= CollisionManager.COLLISION_TOLERANCE) { if (Math.abs(blockMaxY - playerMinY) <= CollisionManager.COLLISION_TOLERANCE) {
playerCollision.translate(0, -0.0625, 0); playerCollision.translate(0, -0.0625, 0);
} }

View file

@ -61,19 +61,15 @@ public class DoorCollision extends BlockCollision {
} }
@Override @Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) {
boolean result = super.correctPosition(session, playerCollision); boolean result = super.correctPosition(session, x, y, z, playerCollision);
// Hack to prevent false positives // Hack to prevent false positives
playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001); playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001); playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001); playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001);
// Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock) // Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock)
if (this.checkIntersection(playerCollision)) { if (this.checkIntersection(x, y, z, playerCollision)) {
Vector3i blockPos = this.position.get();
int x = blockPos.getX();
int z = blockPos.getZ();
switch (facing) { switch (facing) {
case 1 -> playerCollision.setMiddleZ(z + 0.5125); // North case 1 -> playerCollision.setMiddleZ(z + 0.5125); // North
case 2 -> playerCollision.setMiddleX(x + 0.5125); // East case 2 -> playerCollision.setMiddleX(x + 0.5125); // East

View file

@ -41,12 +41,12 @@ public class ScaffoldingCollision extends BlockCollision {
} }
@Override @Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) {
// Hack to not check below the player // Hack to not check below the player
playerCollision.setSizeY(playerCollision.getSizeY() - 0.001); playerCollision.setSizeY(playerCollision.getSizeY() - 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002); playerCollision.setMiddleY(playerCollision.getMiddleY() + 0.002);
boolean intersected = this.checkIntersection(playerCollision); boolean intersected = this.checkIntersection(x, y, z, playerCollision);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.001); playerCollision.setSizeY(playerCollision.getSizeY() + 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002); playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002);
@ -59,7 +59,7 @@ public class ScaffoldingCollision extends BlockCollision {
playerCollision.setSizeY(playerCollision.getSizeY() + 0.001); playerCollision.setSizeY(playerCollision.getSizeY() + 0.001);
playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002); playerCollision.setMiddleY(playerCollision.getMiddleY() - 0.002);
if (this.checkIntersection(playerCollision)) { if (this.checkIntersection(x, y, z, playerCollision)) {
session.getCollisionManager().setOnScaffolding(true); session.getCollisionManager().setOnScaffolding(true);
} }

View file

@ -46,13 +46,13 @@ public class SnowCollision extends BlockCollision {
// Needs to run before the main correction code or it can move the player into blocks // Needs to run before the main correction code or it can move the player into blocks
// This is counteracted by the main collision code pushing them out // This is counteracted by the main collision code pushing them out
@Override @Override
public void beforeCorrectPosition(BoundingBox playerCollision) { public void beforeCorrectPosition(int x, int y, int z, BoundingBox playerCollision) {
// In Bedrock, snow layers round down to half blocks but you can't sink into them at all // In Bedrock, snow layers round down to half blocks but you can't sink into them at all
// This means the collision each half block reaches above where it should be on Java so the player has to be // This means the collision each half block reaches above where it should be on Java so the player has to be
// pushed down // pushed down
if (layers == 4 || layers == 8) { if (layers == 4 || layers == 8) {
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
double boxMaxY = (boundingBoxes[0].getMiddleY() + position.get().getY()) + (boundingBoxes[0].getSizeY() / 2); double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2);
// If the player is in the buggy area, push them down // If the player is in the buggy area, push them down
if (playerMinY > boxMaxY && if (playerMinY > boxMaxY &&
playerMinY <= (boxMaxY + 0.125)) { playerMinY <= (boxMaxY + 0.125)) {
@ -62,7 +62,7 @@ public class SnowCollision extends BlockCollision {
} }
@Override @Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) {
if (layers == 1) { if (layers == 1) {
// 1 layer of snow does not have collision // 1 layer of snow does not have collision
return true; return true;
@ -72,9 +72,9 @@ public class SnowCollision extends BlockCollision {
playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001); playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001); playerCollision.setSizeZ(playerCollision.getSizeZ() - 0.0001);
if (this.checkIntersection(playerCollision)) { if (this.checkIntersection(x, y, z, playerCollision)) {
double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2);
double boxMaxY = (boundingBoxes[0].getMiddleY() + position.get().getY()) + (boundingBoxes[0].getSizeY() / 2); double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2);
// If the player actually can't step onto it (they can step onto it from other snow layers) // If the player actually can't step onto it (they can step onto it from other snow layers)
if ((boxMaxY - playerMinY) > 0.5) { if ((boxMaxY - playerMinY) > 0.5) {
// Cancel the movement // Cancel the movement
@ -85,6 +85,6 @@ public class SnowCollision extends BlockCollision {
playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001); playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001);
playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001); playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001);
playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001); playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001);
return super.correctPosition(session, playerCollision); return super.correctPosition(session, x, y, z, playerCollision);
} }
} }

View file

@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.collision.translators; package org.geysermc.connector.network.translators.collision.translators;
import com.nukkitx.math.vector.Vector3i;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.BoundingBox;
@ -69,15 +68,10 @@ public class TrapdoorCollision extends BlockCollision {
} }
@Override @Override
public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { public boolean correctPosition(GeyserSession session, int x, int y, int z, BoundingBox playerCollision) {
boolean result = super.correctPosition(session, playerCollision); boolean result = super.correctPosition(session, x, y, z, playerCollision);
// Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock) // Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock)
if (this.checkIntersection(playerCollision)) { if (this.checkIntersection(x, y, z, playerCollision)) {
Vector3i blockPos = this.position.get();
int x = blockPos.getX();
int y = blockPos.getY();
int z = blockPos.getZ();
switch (facing) { switch (facing) {
case 1: // North case 1: // North
playerCollision.setMiddleZ(z + 0.5125); playerCollision.setMiddleZ(z + 0.5125);

View file

@ -526,16 +526,7 @@ public class PistonBlockEntity {
} }
private BlockCollision getCollision(Vector3i blockPos) { private BlockCollision getCollision(Vector3i blockPos) {
int blockId = getAttachedBlockId(blockPos); return BlockUtils.getCollision(getAttachedBlockId(blockPos));
if (blockId != BlockStateValues.JAVA_AIR_ID) {
double movementProgress = progress;
if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
movementProgress = 1f - progress;
}
Vector3d offset = getMovement().toDouble().mul(movementProgress);
return BlockUtils.getCollision(blockId, blockPos, offset);
}
return null;
} }
/** /**
@ -550,8 +541,15 @@ public class PistonBlockEntity {
public double computeCollisionOffset(Vector3i blockPos, BoundingBox boundingBox, Axis axis, double movement) { public double computeCollisionOffset(Vector3i blockPos, BoundingBox boundingBox, Axis axis, double movement) {
BlockCollision blockCollision = getCollision(blockPos); BlockCollision blockCollision = getCollision(blockPos);
if (blockCollision != null) { if (blockCollision != null) {
double adjustedMovement = blockCollision.computeCollisionOffset(boundingBox, axis, movement); double movementProgress = progress;
blockCollision.reset(); if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
movementProgress = 1f - progress;
}
Vector3i movementVec = getMovement();
double x = blockPos.getX() + movementVec.getX() * movementProgress;
double y = blockPos.getY() + movementVec.getY() * movementProgress;
double z = blockPos.getZ() + movementVec.getZ() * movementProgress;
double adjustedMovement = blockCollision.computeCollisionOffset(x, y, z, boundingBox, axis, movement);
if (getAttachedBlockId(blockPos) == BlockStateValues.JAVA_SLIME_BLOCK_ID && adjustedMovement != movement) { if (getAttachedBlockId(blockPos) == BlockStateValues.JAVA_SLIME_BLOCK_ID && adjustedMovement != movement) {
session.getPistonCache().setPlayerSlimeCollision(true); session.getPistonCache().setPlayerSlimeCollision(true);
} }
@ -563,9 +561,15 @@ public class PistonBlockEntity {
public boolean checkCollision(Vector3i blockPos, BoundingBox boundingBox) { public boolean checkCollision(Vector3i blockPos, BoundingBox boundingBox) {
BlockCollision blockCollision = getCollision(blockPos); BlockCollision blockCollision = getCollision(blockPos);
if (blockCollision != null) { if (blockCollision != null) {
boolean result = blockCollision.checkIntersection(boundingBox); double movementProgress = progress;
blockCollision.reset(); if (action == PistonValueType.PULLING || action == PistonValueType.CANCELLED_MID_PUSH) {
return result; movementProgress = 1f - progress;
}
Vector3i movementVec = getMovement();
double x = blockPos.getX() + movementVec.getX() * movementProgress;
double y = blockPos.getY() + movementVec.getY() * movementProgress;
double z = blockPos.getZ() + movementVec.getZ() * movementProgress;
return blockCollision.checkIntersection(x, y, z, boundingBox);
} }
return false; return false;
} }

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.utils;
import com.nukkitx.network.util.Preconditions;
import lombok.Getter;
public class BlockPositionIterator {
private final int minX;
private final int minY;
private final int minZ;
private final int sizeX;
private final int sizeZ;
private int i = 0;
private final int maxI;
public BlockPositionIterator(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
Preconditions.checkArgument(maxX >= minX, "maxX is not greater than or equal to minX");
Preconditions.checkArgument(maxY >= minY, "maxY is not greater than or equal to minY");
Preconditions.checkArgument(maxZ >= minZ, "maxZ is not greater than or equal to minZ");
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.sizeX = maxX - minX + 1;
int sizeY = maxY - minY + 1;
this.sizeZ = maxZ - minZ + 1;
this.maxI = sizeX * sizeY * sizeZ;
}
public boolean hasNext() {
return i < maxI;
}
public void next() {
// Iterate in zxy order
i++;
}
public void reset() {
i = 0;
}
public int getX() {
return ((i / sizeZ) % sizeX) + minX;
}
public int getY() {
return (i / sizeZ / sizeX) + minY;
}
public int getZ() {
return (i % sizeZ) + minZ;
}
}

View file

@ -27,7 +27,6 @@ package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.inventory.PlayerInventory;
@ -231,25 +230,15 @@ public class BlockUtils {
return fullJavaIdentifier.substring(0, stateIndex); return fullJavaIdentifier.substring(0, stateIndex);
} }
public static BlockCollision getCollision(int blockId, Vector3i blockPos) { public static BlockCollision getCollision(int blockId) {
BlockCollision collision = Registries.COLLISIONS.get(blockId); return Registries.COLLISIONS.get(blockId);
if (collision != null) {
collision.setPosition(blockPos);
collision.setPositionOffset(null);
}
return collision;
}
public static BlockCollision getCollision(int blockId, Vector3i blockPos, Vector3d blockOffset) {
BlockCollision collision = Registries.COLLISIONS.get(blockId);
if (collision != null) {
collision.setPosition(blockPos);
collision.setPositionOffset(blockOffset);
}
return collision;
} }
public static BlockCollision getCollisionAt(GeyserSession session, Vector3i blockPos) { public static BlockCollision getCollisionAt(GeyserSession session, Vector3i blockPos) {
return getCollision(session.getConnector().getWorldManager().getBlockAt(session, blockPos), blockPos); return getCollision(session.getConnector().getWorldManager().getBlockAt(session, blockPos));
}
public static BlockCollision getCollisionAt(GeyserSession session, int x, int y, int z) {
return getCollision(session.getConnector().getWorldManager().getBlockAt(session, x, y, z));
} }
} }