diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java index a903d1676..40f4fa496 100644 --- a/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java +++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/component/CustomBlockComponents.java @@ -51,6 +51,13 @@ public interface CustomBlockComponents { */ BoxComponent collisionBox(); + /** + * Gets the extended collision box + * Places an invisible collision block above the block to prevent players from jumping on top of it, if defined + * @return The extended collision box. + */ + BoxComponent extendedCollisionBox(); + /** * Gets the display name component * Equivalent to "minecraft:display_name" @@ -153,6 +160,8 @@ public interface CustomBlockComponents { Builder collisionBox(BoxComponent collisionBox); + Builder extendedCollisionBox(BoxComponent extendedCollisionBox); + Builder displayName(String displayName); Builder geometry(String geometry); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java index ef4679a4f..beb937096 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockComponents.java @@ -47,6 +47,7 @@ import java.util.Set; public class GeyserCustomBlockComponents implements CustomBlockComponents { BoxComponent selectionBox; BoxComponent collisionBox; + BoxComponent extendedCollisionBox; String displayName; String geometry; Map materialInstances; @@ -63,6 +64,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) { this.selectionBox = builder.selectionBox; this.collisionBox = builder.collisionBox; + this.extendedCollisionBox = builder.extendedCollisionBox; this.displayName = builder.displayName; this.geometry = builder.geometry; if (builder.materialInstances.isEmpty()) { @@ -95,6 +97,11 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return collisionBox; } + @Override + public BoxComponent extendedCollisionBox() { + return extendedCollisionBox; + } + @Override public String displayName() { return displayName; @@ -158,6 +165,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { public static class CustomBlockComponentsBuilder implements Builder { protected BoxComponent selectionBox; protected BoxComponent collisionBox; + protected BoxComponent extendedCollisionBox; protected String displayName; protected String geometry; protected final Object2ObjectMap materialInstances = new Object2ObjectOpenHashMap<>(); @@ -203,6 +211,13 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents { return this; } + @Override + public Builder extendedCollisionBox(BoxComponent extendedCollisionBox) { + validateBox(extendedCollisionBox); + this.extendedCollisionBox = extendedCollisionBox; + return this; + } + @Override public Builder displayName(String displayName) { this.displayName = displayName; diff --git a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java index 67963ce26..9bf8ef623 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockState; +import org.geysermc.geyser.api.block.custom.component.BoxComponent; import org.geysermc.geyser.registry.loader.CollisionRegistryLoader; import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.registry.populator.BlockRegistryPopulator; @@ -111,6 +112,11 @@ public class BlockRegistries { */ public static final SimpleMappedRegistry CUSTOM_BLOCK_ITEM_OVERRIDES = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + /** + * A registry which stores Java Ids and the extended collision box that should be placed above. + */ + public static final SimpleMappedRegistry CUSTOM_BLOCK_EXTENDED_COLLISION_BOXES = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + /** * A registry which stores skin texture hashes to custom skull blocks. */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java index 6e5f3846e..12fd3200f 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java +++ b/core/src/main/java/org/geysermc/geyser/registry/mappings/versions/MappingsReader_v1.java @@ -294,10 +294,12 @@ public class MappingsReader_v1 extends MappingsReader { private CustomBlockComponents createCustomBlockComponents(JsonNode node, String stateKey, String name) { // This is needed to find the correct selection box for the given block int id = BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(stateKey, -1); - BoxComponent boxComponent = createBoxComponent(id); + BoxComponent boxComponent = createBoxComponent(id, 0, 16, 0); + BoxComponent extendedboxComponent = createBoxComponent(id, 16, 32, -16); CustomBlockComponents.Builder builder = new CustomBlockComponentsBuilder() .collisionBox(boxComponent) - .selectionBox(boxComponent); + .selectionBox(boxComponent) + .extendedCollisionBox(extendedboxComponent); if (node == null) { // No other components were defined @@ -312,6 +314,11 @@ public class MappingsReader_v1 extends MappingsReader { if (collisionBox != null) { builder.collisionBox(collisionBox); } + BoxComponent extendedCollisionBox = createBoxComponent(node.get("extended_collision_box")); + if (extendedCollisionBox != null) { + builder.extendedCollisionBox(extendedCollisionBox); + } + // We set this to max value by default so that we may dictate the correct destroy time ourselves float destructibleByMining = Float.MAX_VALUE; @@ -404,9 +411,12 @@ public class MappingsReader_v1 extends MappingsReader { /** * Creates a {@link BoxComponent} based on a Java block's collision * @param javaId the block's Java ID + * @param minHeight the minimum height of the box + * @param maxHeight the maximum height of the box + * @param heightTranslation the height translation of the box * @return the {@link BoxComponent} */ - private BoxComponent createBoxComponent(int javaId) { + private BoxComponent createBoxComponent(int javaId, float minHeight, float maxHeight, float heightTranslation) { // Some blocks (e.g. plants) have no collision box BlockCollision blockCollision = BlockUtils.getCollision(javaId); if (blockCollision == null) { @@ -425,11 +435,11 @@ public class MappingsReader_v1 extends MappingsReader { // These could be placed above the block when a custom block exceeds this limit // I am hopeful this will be extended slightly since the geometry of blocks can be 1.875^3 float cornerX = MathUtils.clamp((float) boundingBox.getMiddleX() * 16 - 8 - offsetX, -8, 8); - float cornerY = MathUtils.clamp((float) boundingBox.getMiddleY() * 16 - offsetY, 0, 16); + float cornerY = MathUtils.clamp((float) boundingBox.getMiddleY() * 16 - offsetY + heightTranslation, minHeight, maxHeight); float cornerZ = MathUtils.clamp((float) boundingBox.getMiddleZ() * 16 - 8 - offsetZ, -8, 8); float sizeX = MathUtils.clamp((float) boundingBox.getSizeX() * 16, 0, 16); - float sizeY = MathUtils.clamp((float) boundingBox.getSizeY() * 16, 0, 16); + float sizeY = MathUtils.clamp((float) boundingBox.getSizeY() * 16 + heightTranslation, minHeight, maxHeight); float sizeZ = MathUtils.clamp((float) boundingBox.getSizeZ() * 16, 0, 16); return new BoxComponent(cornerX, cornerY, cornerZ, sizeX, sizeY, sizeZ); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java index 3df84799d..330411fa9 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CustomBlockRegistryPopulator.java @@ -44,6 +44,8 @@ public class CustomBlockRegistryPopulator { Set customBlocks = new ObjectOpenHashSet<>(); Int2ObjectMap blockStateOverrides = new Int2ObjectOpenHashMap<>(); Map customBlockItemOverrides = new HashMap<>(); + Map extendedCollisionBoxes = new HashMap<>(); + GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() { @Override public void registerCustomBlock(@NonNull CustomBlockData customBlockData) { @@ -69,6 +71,10 @@ public class CustomBlockRegistryPopulator { throw new IllegalArgumentException("Custom block is unregistered. Name: " + customBlockState.name()); } CustomBlockState oldBlockState = blockStateOverrides.put(id, customBlockState); + BoxComponent extendedCollisionBox = customBlockState.block().components().extendedCollisionBox(); + if (extendedCollisionBox != null) { + extendedCollisionBoxes.put(id, extendedCollisionBox); + } if (oldBlockState != null) { GeyserImpl.getInstance().getLogger().debug("Duplicate block state override for Java Identifier: " + javaIdentifier + " Old override: " + oldBlockState.name() + " New override: " + customBlockState.name()); @@ -97,6 +103,10 @@ public class CustomBlockRegistryPopulator { block.states().forEach((javaIdentifier, customBlockState) -> { int id = BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(javaIdentifier, -1); blockStateOverrides.put(id, customBlockState); + BoxComponent extendedCollisionBox = customBlockState.block().components().extendedCollisionBox(); + if (extendedCollisionBox != null) { + extendedCollisionBoxes.put(id, extendedCollisionBox); + } }); }); @@ -108,6 +118,9 @@ public class CustomBlockRegistryPopulator { BlockRegistries.CUSTOM_BLOCK_ITEM_OVERRIDES.set(customBlockItemOverrides); GeyserImpl.getInstance().getLogger().info("Registered " + customBlockItemOverrides.size() + " custom block item overrides."); + + BlockRegistries.CUSTOM_BLOCK_EXTENDED_COLLISION_BOXES.set(extendedCollisionBoxes); + GeyserImpl.getInstance().getLogger().info("Registered " + extendedCollisionBoxes.size() + " custom block extended collision boxes."); } /**