diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 66cfe386d..e798ad468 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -97,10 +97,13 @@ public class FishingHookEntity extends ThrowableEntity { boolean collided = false; for (Vector3i blockPos : collidableBlocks) { int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos); - BlockCollision blockCollision = BlockUtils.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ()); - if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) { - // TODO Push bounding box out of collision to improve movement - collided = true; + BlockCollision blockCollision = BlockUtils.getCollision(blockID, blockPos); + if (blockCollision != null) { + if (blockCollision.checkIntersection(boundingBox)) { + // TODO Push bounding box out of collision to improve movement + collided = true; + } + blockCollision.setPosition(null); } int waterLevel = BlockStateValues.getWaterLevel(blockID); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java index a28a39271..52754f4a4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java @@ -220,23 +220,21 @@ public class CollisionManager { // Used when correction code needs to be run before the main correction for (Vector3i blockPos : collidableBlocks) { - BlockCollision blockCollision = BlockUtils.getCollisionAt( - session, blockPos.getX(), blockPos.getY(), blockPos.getZ() - ); + BlockCollision blockCollision = BlockUtils.getCollisionAt(session, blockPos); if (blockCollision != null) { blockCollision.beforeCorrectPosition(playerBoundingBox); + blockCollision.setPosition(null); } } // Main correction code for (Vector3i blockPos : collidableBlocks) { - BlockCollision blockCollision = BlockUtils.getCollisionAt( - session, blockPos.getX(), blockPos.getY(), blockPos.getZ() - ); + BlockCollision blockCollision = BlockUtils.getCollisionAt(session, blockPos); if (blockCollision != null) { if (!blockCollision.correctPosition(session, playerBoundingBox)) { return false; } + blockCollision.setPosition(null); } } @@ -251,7 +249,7 @@ public class CollisionManager { */ public boolean isUnderSlab() { Vector3i position = session.getPlayerEntity().getPosition().toInt(); - BlockCollision collision = BlockUtils.getCollisionAt(session, position.getX(), position.getY(), position.getZ()); + BlockCollision collision = BlockUtils.getCollisionAt(session, position); if (collision != null) { // Determine, if the player's bounding box *were* at full height, if it would intersect with the block // at the current location. @@ -262,6 +260,7 @@ public class CollisionManager { playerBoundingBox.setSizeY(EntityType.PLAYER.getHeight()); playerBoundingBox.setMiddleY(standingY); boolean result = collision.checkIntersection(playerBoundingBox); + collision.setPosition(null); playerBoundingBox.setSizeY(originalHeight); playerBoundingBox.setMiddleY(originalY); return result; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java index 7847869ab..8d1b1b925 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/BlockCollision.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.collision.translators; import com.nukkitx.math.vector.Vector3d; +import com.nukkitx.math.vector.Vector3i; import lombok.EqualsAndHashCode; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; @@ -36,11 +37,10 @@ import org.geysermc.connector.network.translators.collision.BoundingBox; public class BlockCollision { @Getter - protected BoundingBox[] boundingBoxes; + protected final BoundingBox[] boundingBoxes; - protected int x; - protected int y; - protected int z; + @EqualsAndHashCode.Exclude + protected final ThreadLocal position; /** * This is used for the step up logic. @@ -51,7 +51,6 @@ public class BlockCollision { * I didn't just set it for beds because other collision may also be slightly raised off the ground. * If this causes any problems, change this back to 0 and add an exception for beds. */ - @EqualsAndHashCode.Exclude protected double pushUpTolerance = 1; /** @@ -59,10 +58,13 @@ public class BlockCollision { */ protected double pushAwayTolerance = CollisionManager.COLLISION_TOLERANCE * 1.1; - public void setPosition(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; + protected BlockCollision(BoundingBox[] boxes) { + this.boundingBoxes = boxes; + this.position = new ThreadLocal<>(); + } + + public void setPosition(Vector3i newPosition) { + this.position.set(newPosition); } /** @@ -78,6 +80,11 @@ public class BlockCollision { * This functionality is currently only used in 6 or 7 layer snow */ public boolean correctPosition(GeyserSession session, 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); for (BoundingBox b : this.boundingBoxes) { double boxMinY = (b.getMiddleY() + y) - (b.getSizeY() / 2); @@ -103,27 +110,34 @@ public class BlockCollision { playerCollision.getMiddleY() - y, playerCollision.getMiddleZ() - z); + // The ULP should give an upper bound on the floating point error + double xULP = Math.ulp((float) Math.max(Math.abs(playerCollision.getMiddleX()) + playerCollision.getSizeX() / 2.0, Math.abs(x) + 1)); + double zULP = Math.ulp((float) Math.max(Math.abs(playerCollision.getMiddleZ()) + playerCollision.getSizeZ() / 2.0, Math.abs(z) + 1)); + + double xPushAwayTolerance = Math.max(pushAwayTolerance, xULP); + double zPushAwayTolerance = Math.max(pushAwayTolerance, zULP); + double northFaceZPos = b.getMiddleZ() - (b.getSizeZ() / 2); double translateDistance = northFaceZPos - relativePlayerPosition.getZ() - (playerCollision.getSizeZ() / 2); - if (Math.abs(translateDistance) < pushAwayTolerance) { + if (Math.abs(translateDistance) < zPushAwayTolerance) { playerCollision.translate(0, 0, translateDistance); } double southFaceZPos = b.getMiddleZ() + (b.getSizeZ() / 2); translateDistance = southFaceZPos - relativePlayerPosition.getZ() + (playerCollision.getSizeZ() / 2); - if (Math.abs(translateDistance) < pushAwayTolerance) { + if (Math.abs(translateDistance) < zPushAwayTolerance) { playerCollision.translate(0, 0, translateDistance); } double eastFaceXPos = b.getMiddleX() + (b.getSizeX() / 2); translateDistance = eastFaceXPos - relativePlayerPosition.getX() + (playerCollision.getSizeX() / 2); - if (Math.abs(translateDistance) < pushAwayTolerance) { + if (Math.abs(translateDistance) < xPushAwayTolerance) { playerCollision.translate(translateDistance, 0, 0); } double westFaceXPos = b.getMiddleX() - (b.getSizeX() / 2); translateDistance = westFaceXPos - relativePlayerPosition.getX() - (playerCollision.getSizeX() / 2); - if (Math.abs(translateDistance) < pushAwayTolerance) { + if (Math.abs(translateDistance) < xPushAwayTolerance) { playerCollision.translate(translateDistance, 0, 0); } @@ -143,6 +157,11 @@ public class BlockCollision { } public boolean checkIntersection(BoundingBox playerCollision) { + Vector3i blockPos = this.position.get(); + int x = blockPos.getX(); + int y = blockPos.getY(); + int z = blockPos.getZ(); + for (BoundingBox b : boundingBoxes) { if (b.checkIntersection(x, y, z, playerCollision)) { return true; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DirtPathCollision.java similarity index 75% rename from connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java rename to connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DirtPathCollision.java index 4d59171e5..aa9a082fa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/GrassPathCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DirtPathCollision.java @@ -25,24 +25,26 @@ package org.geysermc.connector.network.translators.collision.translators; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.translators.collision.BoundingBox; +import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.collision.CollisionRemapper; -@CollisionRemapper(regex = "^grass_path$", passDefaultBoxes = true) -public class GrassPathCollision extends BlockCollision { - public GrassPathCollision(String params, BoundingBox[] defaultBoxes) { - super(); - boundingBoxes = defaultBoxes; +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^dirt_path$", passDefaultBoxes = true) +public class DirtPathCollision extends BlockCollision { + public DirtPathCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); } // 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 @Override public void beforeCorrectPosition(BoundingBox playerCollision) { - // In Bedrock, grass paths are small 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); - // If the player is in the buggy area, push them down - if (playerMinY == y + 1) { + double blockMaxY = position.get().getY() + 1; + if (Math.abs(blockMaxY - playerMinY) <= CollisionManager.COLLISION_TOLERANCE) { playerCollision.translate(0, -0.0625, 0); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java index c98269989..cdfeec8cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/DoorCollision.java @@ -25,10 +25,13 @@ package org.geysermc.connector.network.translators.collision.translators; +import com.nukkitx.math.vector.Vector3i; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.CollisionRemapper; +@EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "_door$", usesParams = true, passDefaultBoxes = true) public class DoorCollision extends BlockCollision { /** @@ -40,8 +43,7 @@ public class DoorCollision extends BlockCollision { private int facing; public DoorCollision(String params, BoundingBox[] defaultBoxes) { - super(); - boundingBoxes = defaultBoxes; + super(defaultBoxes); if (params.contains("facing=north")) { facing = 1; } else if (params.contains("facing=east")) { @@ -68,18 +70,22 @@ public class DoorCollision extends BlockCollision { // Check for door bug (doors are 0.1875 blocks thick on Java but 0.1825 blocks thick on Bedrock) if (this.checkIntersection(playerCollision)) { + Vector3i blockPos = this.position.get(); + int x = blockPos.getX(); + int z = blockPos.getZ(); + switch (facing) { case 1: // North - playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.5125); + playerCollision.setMiddleZ(z + 0.5125); break; case 2: // East - playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.5125); + playerCollision.setMiddleX(x + 0.5125); break; case 3: // South - playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.4875); + playerCollision.setMiddleZ(z + 0.4875); break; case 4: // West - playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.4875); + playerCollision.setMiddleX(x + 0.4875); break; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java deleted file mode 100644 index 0a8a6a00b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.network.translators.collision.translators; - -import org.geysermc.connector.network.translators.collision.BoundingBox; - -public class EmptyCollision extends BlockCollision { - public EmptyCollision(String params) { - super(); - boundingBoxes = new BoundingBox[0]; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java index b31dd9190..8a49a28af 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/OtherCollision.java @@ -25,29 +25,13 @@ package org.geysermc.connector.network.translators.collision.translators; -import com.fasterxml.jackson.databind.node.ArrayNode; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.translators.collision.BoundingBox; -import java.util.Arrays; -import java.util.Comparator; - +@EqualsAndHashCode(callSuper = true) public class OtherCollision extends BlockCollision { - public OtherCollision(ArrayNode collisionList) { - super(); - boundingBoxes = new BoundingBox[collisionList.size()]; - - for (int i = 0; i < collisionList.size(); i++) { - ArrayNode collisionBoxArray = (ArrayNode) collisionList.get(i); - boundingBoxes[i] = new BoundingBox(collisionBoxArray.get(0).asDouble(), - collisionBoxArray.get(1).asDouble(), - collisionBoxArray.get(2).asDouble(), - collisionBoxArray.get(3).asDouble(), - collisionBoxArray.get(4).asDouble(), - collisionBoxArray.get(5).asDouble()); - } - - // Sorting by lowest Y first fixes some bugs - Arrays.sort(boundingBoxes, Comparator.comparingDouble(BoundingBox::getMiddleY)); + public OtherCollision(BoundingBox[] boundingBoxes) { + super(boundingBoxes); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java index ba997d305..06cb0265e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/ScaffoldingCollision.java @@ -25,6 +25,7 @@ package org.geysermc.connector.network.translators.collision.translators; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.CollisionRemapper; @@ -32,11 +33,11 @@ import org.geysermc.connector.network.translators.collision.CollisionRemapper; /** * In order for scaffolding to work on Bedrock, entity flags need to be sent to the player */ +@EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "^scaffolding$", usesParams = true, passDefaultBoxes = true) public class ScaffoldingCollision extends BlockCollision { public ScaffoldingCollision(String params, BoundingBox[] defaultBoxes) { - super(); - boundingBoxes = defaultBoxes; + super(defaultBoxes); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java index 37ea4a1ba..471969d89 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SnowCollision.java @@ -25,37 +25,20 @@ package org.geysermc.connector.network.translators.collision.translators; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.CollisionRemapper; -import java.util.regex.Pattern; -import java.util.regex.Matcher; - -@CollisionRemapper(regex = "^snow$", usesParams = true) +@EqualsAndHashCode(callSuper = true) +@CollisionRemapper(regex = "^snow$", passDefaultBoxes = true, usesParams = true) public class SnowCollision extends BlockCollision { private final int layers; - public SnowCollision(String params) { - super(); - Pattern layersPattern = Pattern.compile("layers=([0-8])"); - Matcher matcher = layersPattern.matcher(params); - //noinspection ResultOfMethodCallIgnored - matcher.find(); - - // Hitbox is 1 layer less (you sink in 1 layer) - layers = Integer.parseInt(matcher.group(1)); - - if (layers > 1) { - boundingBoxes = new BoundingBox[] { - // Take away 1 because you can go 1 layer into snow layers - new BoundingBox(0.5, ((layers - 1) * 0.125) / 2, 0.5, - 1, (layers - 1) * 0.125, 1) - }; - } else { - // Single layers have no collision - boundingBoxes = new BoundingBox[0]; - } + public SnowCollision(String params, BoundingBox[] defaultBoxes) { + super(defaultBoxes); + int layerCharIndex = params.indexOf("=") + 1; + layers = Integer.parseInt(params.substring(layerCharIndex, layerCharIndex + 1)); pushUpTolerance = 0.125; } @@ -69,7 +52,7 @@ public class SnowCollision extends BlockCollision { // pushed down if (layers == 4 || layers == 8) { double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); - double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2); + double boxMaxY = (boundingBoxes[0].getMiddleY() + position.get().getY()) + (boundingBoxes[0].getSizeY() / 2); // If the player is in the buggy area, push them down if (playerMinY > boxMaxY && playerMinY <= (boxMaxY + 0.125)) { @@ -80,6 +63,10 @@ public class SnowCollision extends BlockCollision { @Override public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { + if (layers == 1) { + // 1 layer of snow does not have collision + return true; + } // Hack to prevent false positives playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001); playerCollision.setSizeY(playerCollision.getSizeY() - 0.0001); @@ -87,7 +74,7 @@ public class SnowCollision extends BlockCollision { if (this.checkIntersection(playerCollision)) { double playerMinY = playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2); - double boxMaxY = (boundingBoxes[0].getMiddleY() + y) + (boundingBoxes[0].getSizeY() / 2); + double boxMaxY = (boundingBoxes[0].getMiddleY() + position.get().getY()) + (boundingBoxes[0].getSizeY() / 2); // If the player actually can't step onto it (they can step onto it from other snow layers) if ((boxMaxY - playerMinY) > 0.5) { // Cancel the movement diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java index 05791501d..d96e7d588 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SolidCollision.java @@ -25,15 +25,16 @@ package org.geysermc.connector.network.translators.collision.translators; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.CollisionRemapper; +@EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "shulker_box$") // These have no collision in the mappings as it depends on the NBT data public class SolidCollision extends BlockCollision { public SolidCollision(String params) { - super(); - boundingBoxes = new BoundingBox[]{ - new BoundingBox(0.5, 0.5, 0.5, 1, 1, 1) - }; + super(new BoundingBox[] { + new BoundingBox(0.5, 0.5, 0.5, 1, 1, 1) + }); } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SpawnerCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SpawnerCollision.java index 6999a12b7..867d4b412 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SpawnerCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/SpawnerCollision.java @@ -25,8 +25,10 @@ package org.geysermc.connector.network.translators.collision.translators; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.translators.collision.CollisionRemapper; +@EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "^spawner$") public class SpawnerCollision extends SolidCollision { public SpawnerCollision(String params) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java index 63e97e1d9..a8a35256f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/TrapdoorCollision.java @@ -25,10 +25,14 @@ package org.geysermc.connector.network.translators.collision.translators; +import com.nukkitx.math.vector.Vector3i; +import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.collision.BoundingBox; +import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.collision.CollisionRemapper; +@EqualsAndHashCode(callSuper = true) @CollisionRemapper(regex = "_trapdoor$", usesParams = true, passDefaultBoxes = true) public class TrapdoorCollision extends BlockCollision { /** @@ -42,8 +46,7 @@ public class TrapdoorCollision extends BlockCollision { private int facing; public TrapdoorCollision(String params, BoundingBox[] defaultBoxes) { - super(); - boundingBoxes = defaultBoxes; + super(defaultBoxes); if (params.contains("open=true")) { if (params.contains("facing=north")) { facing = 1; @@ -68,38 +71,35 @@ public class TrapdoorCollision extends BlockCollision { @Override public boolean correctPosition(GeyserSession session, BoundingBox playerCollision) { boolean result = super.correctPosition(session, playerCollision); - // Hack to prevent false positives - playerCollision.setSizeX(playerCollision.getSizeX() - 0.0001); - playerCollision.setSizeY(playerCollision.getSizeY() - 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) if (this.checkIntersection(playerCollision)) { + Vector3i blockPos = this.position.get(); + int x = blockPos.getX(); + int y = blockPos.getY(); + int z = blockPos.getZ(); + switch (facing) { case 1: // North - playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.5125); + playerCollision.setMiddleZ(z + 0.5125); + break; + case 2: // East + playerCollision.setMiddleX(x + 0.5125); break; case 3: // South - playerCollision.setMiddleZ(Math.floor(playerCollision.getMiddleZ()) + 0.4875); + playerCollision.setMiddleZ(z + 0.4875); break; case 4: // West - playerCollision.setMiddleX(Math.floor(playerCollision.getMiddleX()) + 0.4875); + playerCollision.setMiddleX(x + 0.4875); + break; + case 5: + // Up-facing trapdoors are handled by the step-up check break; case 6: // Down - playerCollision.setMiddleY(Math.floor( - playerCollision.getMiddleY() - (playerCollision.getSizeY() / 2) - ) + 0.0125 + (playerCollision.getSizeY() / 2)); - break; - case 2: - case 5: - // Up-facing and east-facing trapdoors work fine + // (top y of trap door) - (trap door thickness) = top y of player + playerCollision.setMiddleY(y + 1 - (3.0 / 16.0) - playerCollision.getSizeY() / 2.0 - CollisionManager.COLLISION_TOLERANCE); break; } } - - playerCollision.setSizeX(playerCollision.getSizeX() + 0.0001); - playerCollision.setSizeY(playerCollision.getSizeY() + 0.0001); - playerCollision.setSizeZ(playerCollision.getSizeZ() + 0.0001); return result; } } diff --git a/connector/src/main/java/org/geysermc/connector/registry/BlockRegistries.java b/connector/src/main/java/org/geysermc/connector/registry/BlockRegistries.java index 58735a2b8..09069eb5a 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/BlockRegistries.java +++ b/connector/src/main/java/org/geysermc/connector/registry/BlockRegistries.java @@ -25,6 +25,7 @@ package org.geysermc.connector.registry; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -54,7 +55,7 @@ public class BlockRegistries { * A mapped registry which stores Java IDs to {@link BlockMapping}, containing miscellaneous information about * blocks and their behavior in many cases. */ - public static final SimpleMappedRegistry JAVA_BLOCKS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + public static final MappedRegistry> JAVA_BLOCKS = MappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); /** * A (bi)mapped registry containing the Java IDs to identifiers. diff --git a/connector/src/main/java/org/geysermc/connector/registry/loader/CollisionRegistryLoader.java b/connector/src/main/java/org/geysermc/connector/registry/loader/CollisionRegistryLoader.java index 56976edb0..049f1d726 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/loader/CollisionRegistryLoader.java +++ b/connector/src/main/java/org/geysermc/connector/registry/loader/CollisionRegistryLoader.java @@ -29,23 +29,22 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.collision.BoundingBox; import org.geysermc.connector.network.translators.collision.CollisionRemapper; import org.geysermc.connector.network.translators.collision.translators.BlockCollision; -import org.geysermc.connector.network.translators.collision.translators.EmptyCollision; import org.geysermc.connector.network.translators.collision.translators.OtherCollision; import org.geysermc.connector.network.translators.collision.translators.SolidCollision; import org.geysermc.connector.registry.BlockRegistries; +import org.geysermc.connector.registry.type.BlockMapping; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.connector.utils.Object2IntBiMap; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; -import java.util.IdentityHashMap; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; /** @@ -68,35 +67,44 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader collisionList; try { - collisionList = (ArrayNode) GeyserConnector.JSON_MAPPER.readTree(stream); + ArrayNode collisionNode = (ArrayNode) GeyserConnector.JSON_MAPPER.readTree(stream); + collisionList = loadBoundingBoxes(collisionNode); } catch (Exception e) { throw new AssertionError("Unable to load collision data", e); } - Object2IntBiMap javaIdBlockMap = BlockRegistries.JAVA_IDENTIFIERS.get(); + Int2ObjectMap blockMap = BlockRegistries.JAVA_BLOCKS.get(); + + // Map of unique collisions to its instance + Map collisionInstances = new Object2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry entry : blockMap.int2ObjectEntrySet()) { + BlockCollision newCollision = instantiateCollision(entry.getValue(), annotationMap, collisionList); - // Map of classes that don't change based on parameters that have already been created - Map, BlockCollision> instantiatedCollision = new IdentityHashMap<>(); - for (Object2IntMap.Entry entry : javaIdBlockMap.object2IntEntrySet()) { - BlockCollision newCollision = instantiateCollision(entry.getKey(), entry.getIntValue(), annotationMap, instantiatedCollision, collisionList); if (newCollision != null) { - instantiatedCollision.put(newCollision.getClass(), newCollision); + // If there's an existing instance equal to this one, use that instead + BlockCollision existingInstance = collisionInstances.get(newCollision); + if (existingInstance != null) { + newCollision = existingInstance; + } else { + collisionInstances.put(newCollision, newCollision); + } } - collisions.put(entry.getIntValue(), newCollision); + + collisions.put(entry.getIntKey(), newCollision); } return collisions; } - private BlockCollision instantiateCollision(String blockID, int numericBlockID, Map, CollisionInfo> annotationMap, Map, BlockCollision> instantiatedCollision, ArrayNode collisionList) { - String[] blockIdParts = blockID.split("\\["); + private BlockCollision instantiateCollision(BlockMapping mapping, Map, CollisionInfo> annotationMap, List collisionList) { + String[] blockIdParts = mapping.getJavaIdentifier().split("\\["); String blockName = blockIdParts[0].replace("minecraft:", ""); String params = ""; - if (blockID.contains("[")) { + if (blockIdParts.length == 2) { params = "[" + blockIdParts[1]; } - int collisionIndex = BlockRegistries.JAVA_BLOCKS.get(numericBlockID).getCollisionIndex(); + int collisionIndex = mapping.getCollisionIndex(); for (Map.Entry, CollisionInfo> collisionRemappers : annotationMap.entrySet()) { Class type = collisionRemappers.getKey(); @@ -105,67 +113,52 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader, BlockCollision> entry : instantiatedCollision.entrySet()) { - if (entry.getValue().equals(collision)) { - collision = entry.getValue(); - break; - } - } - return collision; } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { - e.printStackTrace(); - return null; + throw new RuntimeException(e); } } } // Unless some of the low IDs are changed, which is unlikely, the first item should always be empty collision if (collisionIndex == 0) { - if (instantiatedCollision.containsKey(EmptyCollision.class)) { - return instantiatedCollision.get(EmptyCollision.class); - } else { - return new EmptyCollision(params); - } + return null; } // Unless some of the low IDs are changed, which is unlikely, the second item should always be full collision if (collisionIndex == 1) { - if (instantiatedCollision.containsKey(SolidCollision.class)) { - return instantiatedCollision.get(SolidCollision.class); - } else { - return new SolidCollision(params); - } + return new SolidCollision(params); } + return new OtherCollision(collisionList.get(collisionIndex)); + } - BlockCollision collision = new OtherCollision((ArrayNode) collisionList.get(collisionIndex)); - // If there's an existing instance equal to this one, use that instead - for (Map.Entry, BlockCollision> entry : instantiatedCollision.entrySet()) { - if (entry.getValue().equals(collision)) { - collision = entry.getValue(); - break; + private List loadBoundingBoxes(ArrayNode collisionNode) { + List collisions = new ObjectArrayList<>(); + for (int collisionIndex = 0; collisionIndex < collisionNode.size(); collisionIndex++) { + ArrayNode boundingBoxArray = (ArrayNode) collisionNode.get(collisionIndex); + + BoundingBox[] boundingBoxes = new BoundingBox[boundingBoxArray.size()]; + for (int i = 0; i < boundingBoxArray.size(); i++) { + ArrayNode boxProperties = (ArrayNode) boundingBoxArray.get(i); + boundingBoxes[i] = new BoundingBox(boxProperties.get(0).asDouble(), + boxProperties.get(1).asDouble(), + boxProperties.get(2).asDouble(), + boxProperties.get(3).asDouble(), + boxProperties.get(4).asDouble(), + boxProperties.get(5).asDouble()); } - } - return collision; + // Sorting by lowest Y first fixes some bugs + Arrays.sort(boundingBoxes, Comparator.comparingDouble(BoundingBox::getMiddleY)); + collisions.add(boundingBoxes); + } + return collisions; } /** diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index ad7afddb6..ab935d5ca 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -244,16 +244,15 @@ public class BlockUtils { return fullJavaIdentifier.substring(0, stateIndex); } - // Note: these reuse classes, so don't try to store more than once instance or coordinates will get overwritten - public static BlockCollision getCollision(int blockId, int x, int y, int z) { + public static BlockCollision getCollision(int blockId, Vector3i blockPos) { BlockCollision collision = Registries.COLLISIONS.get(blockId); if (collision != null) { - collision.setPosition(x, y, z); + collision.setPosition(blockPos); } return collision; } - public static BlockCollision getCollisionAt(GeyserSession session, int x, int y, int z) { - return getCollision(session.getConnector().getWorldManager().getBlockAt(session, x, y, z), x, y, z); + public static BlockCollision getCollisionAt(GeyserSession session, Vector3i blockPos) { + return getCollision(session.getConnector().getWorldManager().getBlockAt(session, blockPos), blockPos); } }