From 1d04a61a46bfdbee7fe8d3af70e9a9ba4fb3e296 Mon Sep 17 00:00:00 2001 From: David Choo Date: Fri, 30 Jul 2021 22:35:13 -0400 Subject: [PATCH] Collision Registry (#2430) * Fix trapdoor collision * Add EqualsAndHashCode to all Collision subclasses and shift code around EqualsAndHashCode are required on subclasses otherwise blocks will be assigned an incorrect collision instance. (Doors and trapdoors are mixed and ladder sometimes gets a DoorCollision instance). Added protected constructor to BlockCollision to make boundingBoxes final. Removed EmptyCollision because I don't think it is useful. Moved conversion from ArrayNode to BoundingBoxes[] from OtherCollision to CollisionRegistryLoader Removed regex from SnowCollision and use default bounding boxes. * Deduplicate BlockCollision instances * Create one set of bounding boxes for all BlockCollisions * Don't depend on the player's block position in DoorCollision * Fix dirt path position corrections Grass paths were renamed to dirt path in 1.17 Fix position correction for y=1, y=2, y=255, and y=256 * Increase pushAwayTolerance depending on distance from origin This should fix position corrections for blocks less than 1 unit in length/width at high coordinates. This includes ladders after x 4096 or z 4096 Not too sure about the math here though * Use ThreadLocal for position Hopefully resolves concurrency issues * Remove comment and add layer check to SnowCollision --- .../connector/entity/FishingHookEntity.java | 11 +- .../collision/CollisionManager.java | 13 +- .../collision/translators/BlockCollision.java | 45 +++++-- ...hCollision.java => DirtPathCollision.java} | 18 +-- .../collision/translators/DoorCollision.java | 18 ++- .../collision/translators/EmptyCollision.java | 35 ------ .../collision/translators/OtherCollision.java | 24 +--- .../translators/ScaffoldingCollision.java | 5 +- .../collision/translators/SnowCollision.java | 39 ++---- .../collision/translators/SolidCollision.java | 9 +- .../translators/SpawnerCollision.java | 2 + .../translators/TrapdoorCollision.java | 42 +++---- .../connector/registry/BlockRegistries.java | 3 +- .../loader/CollisionRegistryLoader.java | 113 ++++++++---------- .../geysermc/connector/utils/BlockUtils.java | 9 +- 15 files changed, 174 insertions(+), 212 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/{GrassPathCollision.java => DirtPathCollision.java} (75%) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/collision/translators/EmptyCollision.java 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); } }