diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java index 6d4d7b662..52d90892d 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/CustomBlockData.java @@ -46,6 +46,8 @@ public interface CustomBlockData { @NonNull List permutations(); + @NonNull CustomBlockState defaultBlockState(); + CustomBlockState.@NonNull Builder blockStateBuilder(); interface Builder { 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 6a9faeb5c..85795970a 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 @@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Value; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.block.custom.component.BoxComponent; import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; @@ -38,16 +39,17 @@ import org.jetbrains.annotations.NotNull; import java.util.Map; +@Value public class GeyserCustomBlockComponents implements CustomBlockComponents { - private final BoxComponent selectionBox; - private final BoxComponent collisionBox; - private final String geometry; - private final Map materialInstances; - private final Float destroyTime; - private final Float friction; - private final Integer lightEmission; - private final Integer lightDampening; - private final RotationComponent rotation; + BoxComponent selectionBox; + BoxComponent collisionBox; + String geometry; + Map materialInstances; + Float destroyTime; + Float friction; + Integer lightEmission; + Integer lightDampening; + RotationComponent rotation; private GeyserCustomBlockComponents(CustomBlockComponentsBuilder builder) { this.selectionBox = builder.selectionBox; diff --git a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java index 6cf4bf860..a7085c3e8 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/GeyserCustomBlockData.java @@ -26,8 +26,7 @@ package org.geysermc.geyser.level.block; import it.unimi.dsi.fastutil.objects.*; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Value; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.Constants; @@ -43,7 +42,6 @@ import java.util.List; import java.util.Map; @Value -@AllArgsConstructor(access = AccessLevel.PRIVATE) public class GeyserCustomBlockData implements CustomBlockData { String name; @@ -51,6 +49,43 @@ public class GeyserCustomBlockData implements CustomBlockData { Map> properties; List permutations; + @EqualsAndHashCode.Exclude // Otherwise this will a StackOverflowError in hashCode/equals + CustomBlockState defaultBlockState; + + private GeyserCustomBlockData(CustomBlockDataBuilder builder) { + this.name = builder.name; + if (name == null) { + throw new IllegalStateException("Name must be set"); + } + + this.components = builder.components; + + Object2ObjectMap defaultProperties; + if (!builder.properties.isEmpty()) { + this.properties = Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(builder.properties)); + defaultProperties = new Object2ObjectOpenHashMap<>(this.properties.size()); + for (CustomBlockProperty property : properties.values()) { + if (property.values().isEmpty() || property.values().size() > 16) { + throw new IllegalStateException(property.name() + " must contain 1 to 16 values."); + } + if (property.values().stream().distinct().count() != property.values().size()) { + throw new IllegalStateException(property.name() + " has duplicate values."); + } + defaultProperties.put(property.name(), property.values().get(0)); + } + this.defaultBlockState = new GeyserCustomBlockState(this, Object2ObjectMaps.unmodifiable(defaultProperties)); + } else { + this.properties = Object2ObjectMaps.emptyMap(); + this.defaultBlockState = new GeyserCustomBlockState(this, Object2ObjectMaps.emptyMap()); + } + + if (!builder.permutations.isEmpty()) { + this.permutations = ObjectLists.unmodifiable(new ObjectArrayList<>(builder.permutations)); + } else { + this.permutations = ObjectLists.emptyList(); + } + } + @Override public @NonNull String name() { return name; @@ -76,6 +111,11 @@ public class GeyserCustomBlockData implements CustomBlockData { return permutations; } + @Override + public @NonNull CustomBlockState defaultBlockState() { + return defaultBlockState; + } + @Override public CustomBlockState.@NotNull Builder blockStateBuilder() { return new GeyserCustomBlockState.CustomBlockStateBuilder(this); @@ -125,29 +165,7 @@ public class GeyserCustomBlockData implements CustomBlockData { @Override public CustomBlockData build() { - if (name == null) { - throw new IllegalStateException("Name must be set"); - } - - Object2ObjectMap> properties = Object2ObjectMaps.emptyMap(); - if (!this.properties.isEmpty()) { - properties = Object2ObjectMaps.unmodifiable(new Object2ObjectArrayMap<>(this.properties)); - for (CustomBlockProperty property : properties.values()) { - if (property.values().isEmpty() || property.values().size() > 16) { - throw new IllegalStateException(property.name() + " must contain 1 to 16 values."); - } - if (property.values().stream().distinct().count() != property.values().size()) { - throw new IllegalStateException(property.name() + " has duplicate values."); - } - } - } - - List permutations = ObjectLists.emptyList(); - if (!this.permutations.isEmpty()) { - permutations = ObjectLists.unmodifiable(new ObjectArrayList<>(this.permutations)); - } - - return new GeyserCustomBlockData(name, components, properties, permutations); + return new GeyserCustomBlockData(this); } } } 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 0b37cc313..d88985df2 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 @@ -350,7 +350,7 @@ public class BlockRegistryPopulator { .putString("value", components.geometry()) .build()); } - if (components.materialInstances() != null && !components.materialInstances().isEmpty()) { + if (!components.materialInstances().isEmpty()) { NbtMapBuilder materialsBuilder = NbtMap.builder(); for (Map.Entry entry : components.materialInstances().entrySet()) { MaterialInstance materialInstance = entry.getValue(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java index 390e3a8d9..1f5c62b35 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java @@ -84,13 +84,6 @@ public class CustomSkull { .build(); } - public CustomBlockState getDefaultBlockState() { - return customBlockData.blockStateBuilder() - .intProperty(BITS_A_PROPERTY, 0) - .intProperty(BITS_B_PROPERTY, 0) - .build(); - } - public CustomBlockState getWallBlockState(int wallDirection) { wallDirection = switch (wallDirection) { case 0 -> 2; // South diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java index 694d98e99..1a8062c18 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/item/ItemTranslator.java @@ -38,6 +38,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.inventory.GeyserItemStack; @@ -276,16 +277,24 @@ public abstract class ItemTranslator { */ public static int getBedrockItemId(GeyserSession session, @Nonnull GeyserItemStack itemStack) { if (itemStack.isEmpty()) { - return ItemMapping.AIR.getJavaId(); + return ItemMapping.AIR.getBedrockId(); } int javaId = itemStack.getJavaId(); ItemMapping mapping = ITEM_STACK_TRANSLATORS.getOrDefault(javaId, DEFAULT_TRANSLATOR) .getItemMapping(javaId, itemStack.getNbt(), session.getItemMappings()); + int itemId = mapping.getBedrockId(); + if (mapping == session.getItemMappings().getStoredItems().playerHead()) { + CustomSkull customSkull = getCustomSkull(session, itemStack.getNbt()); + if (customSkull != null) { + itemId = session.getItemMappings().getCustomBlockItemIds().getInt(customSkull.getCustomBlockData()); + } + } + int customItemId = getCustomItem(itemStack.getNbt(), mapping); if (customItemId == -1) { // No custom item - return mapping.getBedrockId(); + return itemId; } else { return customItemId; } @@ -556,29 +565,34 @@ public abstract class ItemTranslator { builder.blockRuntimeId(0); } } - - private static void translatePlayerHead(GeyserSession session, CompoundTag nbt, ItemData.Builder builder) { + + private static CustomSkull getCustomSkull(GeyserSession session, CompoundTag nbt) { if (nbt != null && nbt.contains("SkullOwner")) { if (!(nbt.get("SkullOwner") instanceof CompoundTag skullOwner)) { // It's a username give up d: - return; + return null; } SkinManager.GameProfileData data = SkinManager.GameProfileData.from(skullOwner); if (data == null) { session.getGeyser().getLogger().debug("Not sure how to handle skull head item display. " + nbt); - return; + return null; } String skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1); + return BlockRegistries.CUSTOM_SKULLS.get(skinHash); + } + return null; + } - CustomSkull customSkull = BlockRegistries.CUSTOM_SKULLS.get(skinHash); - if (customSkull != null) { - int itemId = session.getItemMappings().getCustomBlockItemIds().getInt(customSkull.getCustomBlockData()); - int blockRuntimeId = session.getBlockMappings().getCustomBlockStateIds().getInt(customSkull.getDefaultBlockState()); + private static void translatePlayerHead(GeyserSession session, CompoundTag nbt, ItemData.Builder builder) { + CustomSkull customSkull = getCustomSkull(session, nbt); + if (customSkull != null) { + CustomBlockData customBlockData = customSkull.getCustomBlockData(); + int itemId = session.getItemMappings().getCustomBlockItemIds().getInt(customBlockData); + int blockRuntimeId = session.getBlockMappings().getCustomBlockStateIds().getInt(customBlockData.defaultBlockState()); - builder.id(itemId); - builder.blockRuntimeId(blockRuntimeId); - } + builder.id(itemId); + builder.blockRuntimeId(blockRuntimeId); } }