diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockItem.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockItem.java deleted file mode 100644 index 5143246d9d4..00000000000 --- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockItem.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.geysermc.geyser.api.block.custom.nonvanilla; - -import org.checkerframework.checker.index.qual.NonNegative; -import org.checkerframework.checker.nullness.qual.NonNull; - -public record JavaBlockItem(@NonNull String identifier, @NonNegative int javaId, @NonNegative int stackSize) { -} diff --git a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java index 0dd0d3b330b..61b868cc393 100644 --- a/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java +++ b/api/src/main/java/org/geysermc/geyser/api/block/custom/nonvanilla/JavaBlockState.java @@ -70,7 +70,7 @@ public interface JavaBlockState { @Nullable String pistonBehavior(); /** - * Gets whether the block state has block entity + * Gets whether the block state has a block entity * * @return whether the block state has block entity * @deprecated Does not have an effect. If you were using this to diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java index a005fc10351..494946c4a8b 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java @@ -240,7 +240,8 @@ public static final class Builder { private Supplier pickItem; // We'll use this field after building - private Property[] propertyKeys; + private Property[] propertyKeys = null; + private @Nullable Integer javaId = null; /** * For states that we're just tracking for mirroring Java states. @@ -298,11 +299,18 @@ public Builder pickItem(Supplier pickItem) { return this; } + public Builder javaId(int javaId) { + this.javaId = javaId; + return this; + } + private List build(Block block) { if (states.isEmpty()) { - BlockState state = new BlockState(block, BlockRegistries.BLOCK_STATES.get().size()); + if (javaId == null) { + javaId = BlockRegistries.BLOCK_STATES.get().size(); + } + BlockState state = new BlockState(block, javaId); BlockRegistries.BLOCK_STATES.get().add(state); - propertyKeys = null; return List.of(state); } else if (states.size() == 1) { // We can optimize because we don't need to worry about combinations 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 521d6754231..b7316605bf1 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -135,9 +135,9 @@ public static void populate() { CustomSkullRegistryPopulator.populate(); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.PRE_INIT); CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.DEFINITION); - CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.NON_VANILLA_REGISTRATION); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_JAVA); COLLISIONS.load(); + CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.NON_VANILLA_REGISTRATION); CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.VANILLA_REGISTRATION); CustomBlockRegistryPopulator.populate(CustomBlockRegistryPopulator.Stage.CUSTOM_REGISTRATION); BlockRegistryPopulator.populate(BlockRegistryPopulator.Stage.INIT_BEDROCK); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index e86bfe7c4f7..cb002d6f097 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -32,8 +32,6 @@ import com.google.common.collect.Interners; 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; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -52,19 +50,14 @@ import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState; -import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.property.Properties; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.FlowerPotBlock; -import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; import org.geysermc.geyser.registry.type.GeyserBedrockBlock; -import org.geysermc.geyser.util.BlockUtils; -import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.io.DataInputStream; import java.io.InputStream; @@ -72,7 +65,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -114,8 +106,8 @@ public static void populate(Stage stage) { * Stores the raw blocks NBT until it is no longer needed. */ private static List BLOCKS_NBT; - private static int MIN_CUSTOM_RUNTIME_ID = -1; - private static int JAVA_BLOCKS_SIZE = -1; + public static int MIN_CUSTOM_RUNTIME_ID = -1; + public static int JAVA_BLOCKS_SIZE = -1; private static void nullifyBlocksNbt() { BLOCKS_NBT = null; @@ -411,19 +403,6 @@ private static void registerJavaBlocks() { throw new AssertionError("Unable to load Java block mappings", e); } - JAVA_BLOCKS_SIZE = BlockRegistries.BLOCK_STATES.get().size(); - - if (!BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().isEmpty()) { - MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId(); - int maxCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().max(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId(); - - if (MIN_CUSTOM_RUNTIME_ID < blocksNbt.size()) { - throw new RuntimeException("Non vanilla custom block state overrides runtime ID must start after the last vanilla block state (" + JAVA_BLOCKS_SIZE + ")"); - } - - JAVA_BLOCKS_SIZE = maxCustomRuntimeID + 1; // Runtime ids start at 0, so we need to add 1 - } - int javaRuntimeId = -1; for (BlockState javaBlockState : BlockRegistries.BLOCK_STATES.get()) { javaRuntimeId++; @@ -432,49 +411,8 @@ private static void registerJavaBlocks() { BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, javaRuntimeId); } - if (!BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().isEmpty()) { - IntSet usedNonVanillaRuntimeIDs = new IntOpenHashSet(); - - for (JavaBlockState javaBlockState : BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet()) { - if (!usedNonVanillaRuntimeIDs.add(javaBlockState.javaId())) { - throw new RuntimeException("Duplicate runtime ID " + javaBlockState.javaId() + " for non vanilla Java block state " + javaBlockState.identifier()); - } - - String javaId = javaBlockState.identifier(); - int stateRuntimeId = javaBlockState.javaId(); - String pistonBehavior = javaBlockState.pistonBehavior(); - - Block.Builder builder = Block.builder() - .destroyTime(javaBlockState.blockHardness()) - .pushReaction(pistonBehavior == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(pistonBehavior)); - if (!javaBlockState.canBreakWithHand()) { - builder.requiresCorrectToolForDrops(); - } - String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier()); - String pickItem = javaBlockState.pickItem(); - Block block = new Block(cleanJavaIdentifier, builder) { - @Override - public ItemStack pickItem(BlockState state) { - if (this.item == null) { - this.item = Registries.JAVA_ITEM_IDENTIFIERS.get(pickItem); - if (this.item == null) { - GeyserImpl.getInstance().getLogger().warning("We could not find item " + pickItem - + " for getting the item for block " + javaBlockState.identifier()); - this.item = Items.AIR; - } - } - return new ItemStack(this.item.javaId()); - } - }; - block.setJavaId(javaBlockState.stateGroupId()); - - BlockRegistries.JAVA_BLOCKS.registerWithAnyIndex(javaBlockState.stateGroupId(), block, Blocks.AIR); - BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId); - BlockRegistries.BLOCK_STATES.register(stateRuntimeId, new BlockState(block, stateRuntimeId)); - } - } - BLOCKS_NBT = blocksNbt; + JAVA_BLOCKS_SIZE = blocksNbt.size(); JsonNode blockInteractionsJson; try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResourceOrThrow("mappings/interactions.json")) { @@ -485,8 +423,6 @@ public ItemStack pickItem(BlockState state) { BlockRegistries.INTERACTIVE.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("always_consumes"))); BlockRegistries.INTERACTIVE_MAY_BUILD.set(toBlockStateSet((ArrayNode) blockInteractionsJson.get("requires_may_build"))); - - BlockRegistries.BLOCK_STATES.freeze(); } private static BitSet toBlockStateSet(ArrayNode node) { 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 a43df3f52cf..46bfcb45eac 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 @@ -27,6 +27,8 @@ 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; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.nbt.NbtMap; @@ -48,23 +50,38 @@ import org.geysermc.geyser.api.block.custom.property.PropertyType; import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomBlocksEvent; import org.geysermc.geyser.api.util.CreativeCategory; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; import org.geysermc.geyser.level.block.GeyserCustomBlockData; import org.geysermc.geyser.level.block.GeyserCustomBlockState; import org.geysermc.geyser.level.block.GeyserGeometryComponent; import org.geysermc.geyser.level.block.GeyserMaterialInstance; +import org.geysermc.geyser.level.block.type.Block; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.level.physics.BoundingBox; +import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.mappings.MappingsConfigReader; import org.geysermc.geyser.registry.type.CustomSkull; +import org.geysermc.geyser.translator.collision.OtherCollision; +import org.geysermc.geyser.util.BlockUtils; import org.geysermc.geyser.util.MathUtils; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.geysermc.geyser.registry.populator.BlockRegistryPopulator.JAVA_BLOCKS_SIZE; +import static org.geysermc.geyser.registry.populator.BlockRegistryPopulator.MIN_CUSTOM_RUNTIME_ID; public class CustomBlockRegistryPopulator { @@ -231,6 +248,73 @@ private static void populateVanilla() { */ private static void populateNonVanilla() { BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.set(NON_VANILLA_BLOCK_STATE_OVERRIDES); + + if (NON_VANILLA_BLOCK_STATE_OVERRIDES.isEmpty()) { + // Nothing left to register, freeze block state registry + BlockRegistries.BLOCK_STATES.freeze(); + return; + } + + MIN_CUSTOM_RUNTIME_ID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().min(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId(); + int maxCustomRuntimeID = BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet().stream().max(Comparator.comparing(JavaBlockState::javaId)).orElseThrow().javaId(); + + if (MIN_CUSTOM_RUNTIME_ID < BlockRegistries.BLOCK_STATES.get().size()) { + throw new RuntimeException("Non vanilla custom block state overrides runtime ID must start after the last vanilla block state (" + JAVA_BLOCKS_SIZE + ")"); + } + + JAVA_BLOCKS_SIZE = maxCustomRuntimeID + 1; // Runtime ids start at 0, so we need to add 1 + + // Now: Vanilla blocks are already loaded and registered; let's load non-vanilla properly too + IntSet usedNonVanillaRuntimeIDs = new IntOpenHashSet(); + + for (JavaBlockState javaBlockState : BlockRegistries.NON_VANILLA_BLOCK_STATE_OVERRIDES.get().keySet()) { + if (!usedNonVanillaRuntimeIDs.add(javaBlockState.javaId())) { + throw new RuntimeException("Duplicate runtime ID " + javaBlockState.javaId() + " for non vanilla Java block state " + javaBlockState.identifier()); + } + + String javaId = javaBlockState.identifier(); + int stateRuntimeId = javaBlockState.javaId(); + String pistonBehavior = javaBlockState.pistonBehavior(); + + Block.Builder builder = Block.builder() + .javaId(stateRuntimeId) + .destroyTime(javaBlockState.blockHardness()) + .pushReaction(pistonBehavior == null ? PistonBehavior.NORMAL : PistonBehavior.getByName(pistonBehavior)); + if (!javaBlockState.canBreakWithHand()) { + builder.requiresCorrectToolForDrops(); + } + String cleanJavaIdentifier = BlockUtils.getCleanIdentifier(javaBlockState.identifier()); + String pickItem = javaBlockState.pickItem(); + Block block = new Block(cleanJavaIdentifier, builder) { + @Override + public ItemStack pickItem(BlockState state) { + if (this.item == null) { + this.item = Registries.JAVA_ITEM_IDENTIFIERS.get(pickItem); + if (this.item == null) { + GeyserImpl.getInstance().getLogger().warning("We could not find item " + pickItem + + " for getting the item for block " + javaBlockState.identifier()); + this.item = Items.AIR; + } + } + return new ItemStack(this.item.javaId()); + } + }; + block.setJavaId(javaBlockState.stateGroupId()); + + BlockRegistries.JAVA_BLOCKS.registerWithAnyIndex(javaBlockState.stateGroupId(), block, Blocks.AIR); + BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId); + + // TODO register different collision types? + BoundingBox[] geyserCollisions = Arrays.stream(javaBlockState.collision()) + .map(box -> new BoundingBox(box.middleX(), box.middleY(), box.middleZ(), + box.sizeX(), box.sizeY(), box.sizeZ())) + .toArray(BoundingBox[]::new); + OtherCollision collision = new OtherCollision(geyserCollisions); + BlockRegistries.COLLISIONS.registerWithAnyIndex(javaBlockState.javaId(), collision, collision); + } + + BlockRegistries.BLOCK_STATES.freeze(); + if (!NON_VANILLA_BLOCK_STATE_OVERRIDES.isEmpty()) { GeyserImpl.getInstance().getLogger().info("Registered " + NON_VANILLA_BLOCK_STATE_OVERRIDES.size() + " non-vanilla block overrides."); }