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
This commit is contained in:
David Choo 2021-07-30 22:35:13 -04:00 committed by GitHub
parent 6f93bbfe21
commit 1d04a61a46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 174 additions and 212 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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<Vector3i> 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;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
});
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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<Integer, BlockMapping> JAVA_BLOCKS = SimpleMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
public static final MappedRegistry<Integer, BlockMapping, Int2ObjectMap<BlockMapping>> JAVA_BLOCKS = MappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
/**
* A (bi)mapped registry containing the Java IDs to identifiers.

View File

@ -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<String,
// Load collision mappings file
InputStream stream = FileUtils.getResource(input.value());
ArrayNode collisionList;
List<BoundingBox[]> 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<String> javaIdBlockMap = BlockRegistries.JAVA_IDENTIFIERS.get();
Int2ObjectMap<BlockMapping> blockMap = BlockRegistries.JAVA_BLOCKS.get();
// Map of unique collisions to its instance
Map<BlockCollision, BlockCollision> collisionInstances = new Object2ObjectOpenHashMap<>();
for (Int2ObjectMap.Entry<BlockMapping> 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<Class<?>, BlockCollision> instantiatedCollision = new IdentityHashMap<>();
for (Object2IntMap.Entry<String> 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<Class<?>, CollisionInfo> annotationMap, Map<Class<?>, BlockCollision> instantiatedCollision, ArrayNode collisionList) {
String[] blockIdParts = blockID.split("\\[");
private BlockCollision instantiateCollision(BlockMapping mapping, Map<Class<?>, CollisionInfo> annotationMap, List<BoundingBox[]> 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<Class<?>, CollisionInfo> collisionRemappers : annotationMap.entrySet()) {
Class<?> type = collisionRemappers.getKey();
@ -105,67 +113,52 @@ public class CollisionRegistryLoader extends MultiResourceRegistryLoader<String,
if (collisionInfo.pattern.matcher(blockName).find() && collisionInfo.paramsPattern.matcher(params).find()) {
try {
if (!annotation.usesParams() && instantiatedCollision.containsKey(type)) {
return instantiatedCollision.get(type);
}
// Return null when empty to save unnecessary checks
if (type == EmptyCollision.class) {
return null;
}
BlockCollision collision;
if (annotation.passDefaultBoxes()) {
// Create an OtherCollision instance and get the bounding boxes
BoundingBox[] defaultBoxes = new OtherCollision((ArrayNode) collisionList.get(collisionIndex)).getBoundingBoxes();
collision = (BlockCollision) type.getDeclaredConstructor(String.class, BoundingBox[].class).newInstance(params, defaultBoxes);
BoundingBox[] defaultBoxes = collisionList.get(collisionIndex);
return (BlockCollision) type.getDeclaredConstructor(String.class, BoundingBox[].class).newInstance(params, defaultBoxes);
} else {
collision = (BlockCollision) type.getDeclaredConstructor(String.class).newInstance(params);
return (BlockCollision) type.getDeclaredConstructor(String.class).newInstance(params);
}
// If there's an existing instance equal to this one, use that instead
for (Map.Entry<Class<?>, 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<Class<?>, BlockCollision> entry : instantiatedCollision.entrySet()) {
if (entry.getValue().equals(collision)) {
collision = entry.getValue();
break;
private List<BoundingBox[]> loadBoundingBoxes(ArrayNode collisionNode) {
List<BoundingBox[]> 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;
}
/**

View File

@ -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);
}
}