diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java index 05659fa42..211457af1 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomBlocksEvent.java @@ -27,10 +27,12 @@ package org.geysermc.geyser.api.event.lifecycle; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.block.custom.CustomBlockData; +import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.api.event.Event; public abstract class GeyserDefineCustomBlocksEvent implements Event { public abstract void registerCustomBlock(@NonNull CustomBlockData customBlockData); + public abstract void registerBlockStateOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState); } 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 8d3312dca..0103e1762 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -25,11 +25,13 @@ package org.geysermc.geyser.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; 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.registry.loader.RegistryLoaders; import org.geysermc.geyser.registry.populator.BlockRegistryPopulator; import org.geysermc.geyser.registry.populator.CustomSkullRegistryPopulator; @@ -90,6 +92,11 @@ public class BlockRegistries { */ public static final ArrayRegistry CUSTOM_BLOCKS = ArrayRegistry.create(RegistryLoaders.empty(() -> new CustomBlockData[] {})); + /** + * A registry which stores Java Ids and the custom block state it should be replaced with. + */ + public static final MappedRegistry> CUSTOM_BLOCK_STATE_OVERRIDES = MappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new)); + /** * A registry which stores skin texture hashes to custom skull blocks. */ diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 4b361ba4f..48740d743 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -64,6 +64,12 @@ import java.util.*; * Holds all the common registries in Geyser. */ public final class Registries { + /** + * A registry holding all the providers. + * This has to be initialized first to allow extensions to access providers during other registry events. + */ + public static final SimpleMappedRegistry, ProviderSupplier> PROVIDERS = SimpleMappedRegistry.create(new IdentityHashMap<>(), ProviderRegistryLoader::new); + /** * A registry holding a CompoundTag of the known entity identifiers. */ @@ -136,11 +142,6 @@ public final class Registries { */ public static final SimpleRegistry> POTION_MIXES; - /** - * A registry holding all the - */ - public static final SimpleMappedRegistry, ProviderSupplier> PROVIDERS = SimpleMappedRegistry.create(new IdentityHashMap<>(), ProviderRegistryLoader::new); - /** * A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value. */ 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 fadc21dae..058cf0661 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,6 +32,8 @@ import com.nukkitx.nbt.*; import com.nukkitx.protocol.bedrock.data.BlockPropertyData; import com.nukkitx.protocol.bedrock.v527.Bedrock_v527; import com.nukkitx.protocol.bedrock.v534.Bedrock_v534; +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.*; @@ -94,12 +96,36 @@ public class BlockRegistryPopulator { if (!GeyserImpl.getInstance().getConfig().isAddCustomBlocks()) { return; } - List customBlocks = new ArrayList<>(); + Set customBlockNames = new HashSet<>(); + Set customBlocks = new HashSet<>(); + Int2ObjectMap blockStateOverrides = new Int2ObjectOpenHashMap<>(); GeyserImpl.getInstance().getEventBus().fire(new GeyserDefineCustomBlocksEvent() { @Override public void registerCustomBlock(@NonNull CustomBlockData customBlockData) { + if (!customBlockNames.add(customBlockData.name())) { + throw new IllegalArgumentException("Another custom block was already registered under the name: " + customBlockData.name()); + } + // TODO validate collision+selection box bounds + // TODO validate names customBlocks.add(customBlockData); } + + @Override + public void registerBlockStateOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState) { + int id = BlockRegistries.JAVA_IDENTIFIERS.getOrDefault(javaIdentifier, -1); + if (id == -1) { + throw new IllegalArgumentException("Unknown Java block state. Identifier: " + javaIdentifier); + } + if (!customBlocks.contains(customBlockState.block())) { + throw new IllegalArgumentException("Custom block is unregistered. Name: " + customBlockState.name()); + } + CustomBlockState oldBlockState = blockStateOverrides.put(id, customBlockState); + if (oldBlockState != null) { + // TODO should this be an error? Allow extensions to query block state overrides? + GeyserImpl.getInstance().getLogger().debug("Duplicate block state override for Java Identifier: " + + javaIdentifier + " Old override: " + oldBlockState.name() + " New override: " + customBlockState.name()); + } + } }); for (CustomSkull customSkull : BlockRegistries.CUSTOM_SKULLS.get().values()) { @@ -108,6 +134,9 @@ public class BlockRegistryPopulator { BlockRegistries.CUSTOM_BLOCKS.set(customBlocks.toArray(new CustomBlockData[0])); GeyserImpl.getInstance().getLogger().debug("Registered " + customBlocks.size() + " custom blocks."); + + BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.set(blockStateOverrides); + GeyserImpl.getInstance().getLogger().debug("Registered " + blockStateOverrides.size() + " custom block overrides."); } private static void generateCustomBlockStates(CustomBlockData customBlock, List blockStates, List customExtBlockStates, int stateVersion) { @@ -247,10 +276,20 @@ public class BlockRegistryPopulator { Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); - int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(buildBedrockState(entry.getValue(), stateVersion, stateMapper), -1); - if (bedrockRuntimeId == -1) { - throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built NBT tag: \n" + - buildBedrockState(entry.getValue(), stateVersion, stateMapper)); + int bedrockRuntimeId; + CustomBlockState blockStateOverride = BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get(javaRuntimeId); + if (blockStateOverride == null) { + bedrockRuntimeId = blockStateOrderedMap.getOrDefault(buildBedrockState(entry.getValue(), stateVersion, stateMapper), -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built NBT tag: \n" + + buildBedrockState(entry.getValue(), stateVersion, stateMapper)); + } + } else { + bedrockRuntimeId = customBlockStateIds.getOrDefault(blockStateOverride, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Custom block override: \n" + + blockStateOverride); + } } switch (javaId) {