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 6ee764514..5d8422499 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 @@ -38,6 +38,8 @@ import java.util.Map; public interface CustomBlockData { @NonNull String name(); + @NonNull String identifier(); + CustomBlockComponents components(); @NonNull Map> properties(); diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/RotationComponent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/RotationComponent.java index e8c66caf6..6fab3c1ba 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/RotationComponent.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/block/custom/component/RotationComponent.java @@ -25,5 +25,5 @@ package org.geysermc.geyser.api.block.custom.component; -public record RotationComponent(float x, float y, float z) { +public record RotationComponent(int x, int y, int z) { } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/GeyserConvertSkullInventoryEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/GeyserConvertSkullInventoryEvent.java deleted file mode 100644 index ff5edea24..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/GeyserConvertSkullInventoryEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.geyser.api.event; - -import lombok.RequiredArgsConstructor; -import org.geysermc.geyser.api.block.custom.CustomBlockData; - -@RequiredArgsConstructor -public class GeyserConvertSkullInventoryEvent implements Event { - private final String skinHash; - - private CustomBlockData replacementBlock; - - public void replaceWithBlock(CustomBlockData customBlockData) { - this.replacementBlock = customBlockData; - } - - public String skinHash() { - return skinHash; - } - - public CustomBlockData replacementBlock() { - return replacementBlock; - } -} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/event/world/GeyserConvertSkullEvent.java b/api/geyser/src/main/java/org/geysermc/geyser/api/event/world/GeyserConvertSkullEvent.java deleted file mode 100644 index b6ec543ac..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/event/world/GeyserConvertSkullEvent.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.geyser.api.event.world; - -import org.geysermc.geyser.api.block.custom.CustomBlockState; -import org.geysermc.geyser.api.event.Cancellable; -import org.geysermc.geyser.api.event.Event; - -public class GeyserConvertSkullEvent implements Event, Cancellable { - private final int x; - private final int y; - private final int z; - - private final boolean onFloor; - - private final WallDirection wallDirection; - private final int floorDirection; - - private final String skinHash; - - private boolean cancelled; - - private CustomBlockState newBlockState; - - public GeyserConvertSkullEvent(int x, int y, int z, boolean onFloor, WallDirection wallDirection, int floorDirection, String skinHash) { - this.x = x; - this.y = y; - this.z = z; - this.onFloor = onFloor; - this.wallDirection = wallDirection; - this.floorDirection = floorDirection; - - if (onFloor && (wallDirection != WallDirection.INVALID || floorDirection == -1)) { - throw new IllegalArgumentException("Skull can't be on the floor and wall at the same time"); - } else if (!onFloor && (wallDirection == WallDirection.INVALID || floorDirection != -1)) { - throw new IllegalArgumentException("Skull can't be on the floor and wall at the same time"); - } - - this.skinHash = skinHash; - } - - public int x() { - return x; - } - - public int y() { - return y; - } - - public int z() { - return z; - } - - public boolean onFloor() { - return onFloor; - } - - public WallDirection wallDirection() { - return wallDirection; - } - - public int floorDirection() { - return floorDirection; - } - - public String skinHash() { - return skinHash; - } - - public void replaceWithBlock(CustomBlockState blockState) { - this.newBlockState = blockState; - } - - public CustomBlockState getNewBlockState() { - return newBlockState; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean cancelled) { - this.cancelled = cancelled; - } - public enum WallDirection { - NORTH, - EAST, - SOUTH, - WEST, - INVALID - - } -} 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 47d129a8d..caf042038 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 @@ -52,6 +52,11 @@ public class GeyserCustomBlockData implements CustomBlockData { return name; } + @Override + public @NonNull String identifier() { + return "geyser:" + name; + } + @Override public CustomBlockComponents components() { return components; 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 d5b5275a6..1d13e1acf 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/BlockRegistries.java @@ -34,6 +34,7 @@ import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.registry.populator.BlockRegistryPopulator; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.util.collection.Object2IntBiMap; /** @@ -78,6 +79,12 @@ public class BlockRegistries { */ public static final ArrayRegistry CUSTOM_BLOCKS = ArrayRegistry.create(RegistryLoaders.empty(() -> new CustomBlockData[] {})); + /** + * A registry which stores skin texture hashes to custom skull blocks. + * TODO add loader/populator + */ + public static final SimpleMappedRegistry CUSTOM_SKULLS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + static { BlockRegistryPopulator.populate(); } 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 4781110f7..e6a210f18 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 @@ -50,6 +50,7 @@ import org.geysermc.geyser.level.physics.PistonBehavior; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.util.BlockUtils; import java.io.DataInputStream; @@ -78,8 +79,6 @@ public class BlockRegistryPopulator { */ private static JsonNode BLOCKS_JSON; - public static final String CUSTOM_PREFIX = ""; - public static void populate() { registerJavaBlocks(); registerCustomBedrockBlocks(); @@ -96,15 +95,19 @@ public class BlockRegistryPopulator { return true; } }); + + for (CustomSkull customSkull : BlockRegistries.CUSTOM_SKULLS.get().values()) { + customBlocks.add(customSkull.getCustomBlockData()); + } + BlockRegistries.CUSTOM_BLOCKS.set(customBlocks.toArray(new CustomBlockData[0])); GeyserImpl.getInstance().getLogger().debug("Registered " + customBlocks.size() + " custom blocks."); } private static void generateCustomBlockStates(CustomBlockData customBlock, List blockStates, List customExtBlockStates, int stateVersion) { - String name = CUSTOM_PREFIX + customBlock.name(); if (customBlock.properties().isEmpty()) { blockStates.add(NbtMap.builder() - .putString("name", name) + .putString("name", customBlock.identifier()) .putInt("version", stateVersion) .putCompound("states", NbtMap.EMPTY) .build()); @@ -116,7 +119,6 @@ public class BlockRegistryPopulator { for (int i = 0; i < totalPermutations; i++) { NbtMapBuilder statesBuilder = NbtMap.builder(); - int permIndex = i; for (CustomBlockProperty property : customBlock.properties().values()) { statesBuilder.put(property.name(), property.values().get(permIndex % property.values().size())); @@ -125,11 +127,11 @@ public class BlockRegistryPopulator { NbtMap states = statesBuilder.build(); blockStates.add(NbtMap.builder() - .putString("name", name) + .putString("name", customBlock.identifier()) .putInt("version", stateVersion) .putCompound("states", states) .build()); - customExtBlockStates.add(new GeyserCustomBlockState(name, states)); + customExtBlockStates.add(new GeyserCustomBlockState(customBlock.name(), states)); } } @@ -168,7 +170,7 @@ public class BlockRegistryPopulator { .putList("permutations", NbtType.COMPOUND, permutations) .putList("properties", NbtType.COMPOUND, properties) .build(); - return new BlockPropertyData(CUSTOM_PREFIX + customBlock.name(), propertyTag); + return new BlockPropertyData(customBlock.identifier(), propertyTag); } private static void registerBedrockBlocks() { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index 150f1b6bd..4cf4f4d29 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -642,7 +642,7 @@ public class ItemRegistryPopulator { customBlockItemIds = new Object2IntOpenHashMap<>(); for (CustomBlockData customBlock : BlockRegistries.CUSTOM_BLOCKS.get()) { int customProtocolId = nextFreeBedrockId++; - String identifier = BlockRegistryPopulator.CUSTOM_PREFIX + customBlock.name(); + String identifier = customBlock.identifier(); entries.put(identifier, new StartGamePacket.ItemEntry(identifier, (short) customProtocolId)); customBlockItemIds.put(customBlock, customProtocolId); 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 new file mode 100644 index 000000000..54bbc6835 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019-2022 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.geyser.registry.type; + +import lombok.Data; +import org.geysermc.geyser.api.block.custom.CustomBlockData; +import org.geysermc.geyser.api.block.custom.CustomBlockPermutation; +import org.geysermc.geyser.api.block.custom.CustomBlockState; +import org.geysermc.geyser.api.block.custom.component.BoxComponent; +import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents; +import org.geysermc.geyser.api.block.custom.component.MaterialInstance; +import org.geysermc.geyser.api.block.custom.component.RotationComponent; +import org.geysermc.geyser.level.block.GeyserCustomBlockComponents; +import org.geysermc.geyser.level.block.GeyserCustomBlockData; +import org.geysermc.geyser.level.block.GeyserCustomBlockPermutation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +@Data +public class CustomSkull { + private final String skinHash; + + private CustomBlockData customBlockData; + + private static final String DIRECTION_PROPERTY = "davchoo:direction"; + private static final String TYPE_PROPERTY = "davchoo:type"; + + private static final int[] ROTATIONS = {0, -90, 180, 90}; + + public CustomSkull(String skinHash) { + this.skinHash = skinHash; + + CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + .destroyTime(1.5f) + .materialInstances(Map.of("*", new MaterialInstance("davchoo." + skinHash + "_player_skin", "alpha_test", true, true))) + .lightFilter(0) + .build(); + + List permutations = new ArrayList<>(); + addDefaultPermutation(permutations); + addFloorPermutations(permutations); + addWallPermutations(permutations); + + customBlockData = new GeyserCustomBlockData.CustomBlockDataBuilder() + .name("player_head_" + skinHash) + .components(components) + .intProperty(DIRECTION_PROPERTY, IntStream.rangeClosed(0, 15).boxed().toList()) + .intProperty(TYPE_PROPERTY, IntStream.rangeClosed(0, 2).boxed().toList()) + .permutations(permutations) + .build(); + } + + public CustomBlockState getDefaultBlockState() { + return customBlockData.blockStateBuilder() + .intProperty(DIRECTION_PROPERTY, 0) + .intProperty(TYPE_PROPERTY, 0) + .build(); + } + + public CustomBlockState getWallBlockState(int wallDirection) { + wallDirection = switch (wallDirection) { + case 0 -> 2; // South + case 90 -> 3; // West + case 180 -> 0; // North + case 270 -> 1; // East + default -> throw new IllegalArgumentException("Unknown skull wall direction: " + wallDirection); + }; + + return customBlockData.blockStateBuilder() + .intProperty(DIRECTION_PROPERTY, wallDirection) + .intProperty(TYPE_PROPERTY, 1) + .build(); + } + + public CustomBlockState getFloorBlockState(int floorRotation) { + return customBlockData.blockStateBuilder() + .intProperty(DIRECTION_PROPERTY, floorRotation) + .intProperty(TYPE_PROPERTY, 2) + .build(); + } + + private void addDefaultPermutation(List permutations) { + CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + .geometry("geometry.davchoo.player_head_hand") + .rotation(new RotationComponent(0, 180, 0)) + .build(); + + String condition = String.format("query.block_property('%s') == 0 && query.block_property('%s') == 0", DIRECTION_PROPERTY, TYPE_PROPERTY); + permutations.add(new GeyserCustomBlockPermutation.CustomBlockPermutationBuilder() + .condition(condition) + .components(components) + .build()); + } + + private void addFloorPermutations(List permutations) { + BoxComponent box = new BoxComponent( + -4, 0, -4, + 8, 8, 8 + ); + + String[] quadrantNames = {"a", "b", "c", "d"}; + + for (int quadrant = 0; quadrant < 4; quadrant++) { + RotationComponent rotation = new RotationComponent(0, ROTATIONS[quadrant], 0); + for (int i = 0; i < 4; i++) { + int floorDirection = 4 * quadrant + i; + CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + .aimCollision(box) + .entityCollision(box) + .geometry("geometry.davchoo.player_head_floor_" + quadrantNames[i]) + .rotation(rotation) + .build(); + + String condition = String.format("query.block_property('%s') == %d && query.block_property('%s') == %d", DIRECTION_PROPERTY, floorDirection, TYPE_PROPERTY, 2); + permutations.add(new GeyserCustomBlockPermutation.CustomBlockPermutationBuilder() + .condition(condition) + .components(components) + .build()); + } + } + } + + private void addWallPermutations(List permutations) { + BoxComponent box = new BoxComponent( + -4, 4, 0, + 8, 8, 8 + ); + + for (int i = 0; i < 4; i++) { + RotationComponent rotation = new RotationComponent(0, ROTATIONS[i], 0); + String condition = String.format("query.block_property('%s') == %d && query.block_property('%s') == %d", DIRECTION_PROPERTY, i, TYPE_PROPERTY, 1); + + CustomBlockComponents components = new GeyserCustomBlockComponents.CustomBlockComponentsBuilder() + .aimCollision(box) + .entityCollision(box) + .geometry("geometry.davchoo.player_head_wall") + .rotation(rotation) + .build(); + + permutations.add(new GeyserCustomBlockPermutation.CustomBlockPermutationBuilder() + .condition(condition) + .components(components) + .build()); + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 62eb38fc9..3c61133c7 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1469,7 +1469,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { startGamePacket.setItemEntries(this.itemMappings.getItemEntries()); startGamePacket.getBlockProperties().addAll(this.blockMappings.getBlockProperties()); - startGamePacket.getExperiments().add(new ExperimentData("vanilla_experiments", true)); startGamePacket.getExperiments().add(new ExperimentData("data_driven_items", true)); startGamePacket.getExperiments().add(new ExperimentData("upcoming_creator_features", true)); startGamePacket.getExperiments().add(new ExperimentData("experimental_molang_features", true)); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java index 133c40337..291790ed8 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/SkullCache.java @@ -27,15 +27,20 @@ package org.geysermc.geyser.session.cache; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Data; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.block.custom.CustomBlockState; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; +import org.geysermc.geyser.level.block.BlockStateValues; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator; +import org.geysermc.geyser.skin.SkinManager; +import java.io.IOException; import java.util.*; public class SkullCache { @@ -73,20 +78,33 @@ public class SkullCache { this.skullRenderDistanceSquared = distance * distance; } - public void putSkull(Vector3i position, String texturesProperty, int blockState) { + public Skull putSkull(Vector3i position, String texturesProperty, int blockState) { Skull skull = skulls.computeIfAbsent(position, Skull::new); - skull.texturesProperty = texturesProperty; + if (!texturesProperty.equals(skull.texturesProperty)) { + skull.texturesProperty = texturesProperty; + try { + SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty); + if (gameProfileData != null && gameProfileData.skinUrl() != null) { + String skinUrl = gameProfileData.skinUrl(); + skull.skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1); + } else { + session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); + skull.skinHash = null; + } + } catch (IOException e) { + session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + position + " Textures: " + texturesProperty); + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + skull.skinHash = null; + } + } skull.blockState = blockState; - if (skull.customRuntimeId != -1) { - skull.customRuntimeId = -1; + skull.customRuntimeId = translateCustomSkull(skull.skinHash, blockState); - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); - updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); - updateBlockPacket.setBlockPosition(position); - updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(blockState)); - updateBlockPacket.setDataLayer(0); - session.sendUpstreamPacket(updateBlockPacket); + if (skull.customRuntimeId != -1) { + reassignSkullEntity(skull); + return skull; } if (skull.entity != null) { @@ -94,10 +112,10 @@ public class SkullCache { } else { if (!cullingEnabled) { assignSkullEntity(skull); - return; + return skull; } if (lastPlayerPosition == null) { - return; + return skull; } skull.distanceSquared = position.distanceSquared(lastPlayerPosition.getX(), lastPlayerPosition.getY(), lastPlayerPosition.getZ()); if (skull.distanceSquared < skullRenderDistanceSquared) { @@ -117,54 +135,22 @@ public class SkullCache { } } } - } - - public void putSkull(Vector3i position, String texturesProperty, int blockState, int customRuntimeId) { - Skull skull = skulls.computeIfAbsent(position, Skull::new); - skull.texturesProperty = texturesProperty; - skull.blockState = blockState; - skull.customRuntimeId = customRuntimeId; - - boolean hadEntity = skull.entity != null; - freeSkullEntity(skull); - - if (cullingEnabled) { - inRangeSkulls.remove(skull); - if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) { - // Reassign entity to the closest skull without an entity - assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1)); - } - } + return skull; } public void removeSkull(Vector3i position) { Skull skull = skulls.remove(position); if (skull != null) { - boolean hadEntity = skull.entity != null; - freeSkullEntity(skull); - - if (cullingEnabled) { - inRangeSkulls.remove(skull); - if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) { - // Reassign entity to the closest skull without an entity - assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1)); - } - } + reassignSkullEntity(skull); } } - public int updateSkull(Vector3i position, int blockState) { - Skull skull = skulls.remove(position); + public Skull updateSkull(Vector3i position, int blockState) { + Skull skull = skulls.get(position); if (skull != null) { - int customRuntimeId = SkullBlockEntityTranslator.translateCustomSkull(session, position, skull.texturesProperty, blockState); - if (customRuntimeId != -1) { - putSkull(position, skull.texturesProperty, blockState, customRuntimeId); - } else { - putSkull(position, skull.texturesProperty, blockState); - } - return customRuntimeId; + putSkull(position, skull.texturesProperty, blockState); } - return -1; + return skull; } public void updateVisibleSkulls() { @@ -239,6 +225,19 @@ public class SkullCache { } } + private void reassignSkullEntity(Skull skull) { + boolean hadEntity = skull.entity != null; + freeSkullEntity(skull); + + if (cullingEnabled) { + inRangeSkulls.remove(skull); + if (hadEntity && inRangeSkulls.size() >= maxVisibleSkulls) { + // Reassign entity to the closest skull without an entity + assignSkullEntity(inRangeSkulls.get(maxVisibleSkulls - 1)); + } + } + } + public void clear() { skulls.clear(); inRangeSkulls.clear(); @@ -247,10 +246,30 @@ public class SkullCache { lastPlayerPosition = null; } + private int translateCustomSkull(String skinHash, int blockState) { + CustomSkull customSkull = BlockRegistries.CUSTOM_SKULLS.get(skinHash); + if (customSkull != null) { + byte floorRotation = BlockStateValues.getSkullRotation(blockState); + CustomBlockState customBlockState; + if (floorRotation == -1) { + // Wall skull + int wallDirection = BlockStateValues.getSkullWallDirections().get(blockState); + customBlockState = customSkull.getWallBlockState(wallDirection); + } else { + customBlockState = customSkull.getFloorBlockState(floorRotation); + } + + return session.getBlockMappings().getCustomBlockStateIds().getOrDefault(customBlockState, -1); + } + return -1; + } + @RequiredArgsConstructor @Data public static class Skull { private String texturesProperty; + private String skinHash; + private int blockState; private int customRuntimeId = -1; private SkullPlayerEntity entity; 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 995408318..a93060411 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,12 +38,11 @@ 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.event.GeyserConvertSkullInventoryEvent; import org.geysermc.geyser.api.item.custom.CustomItemOptions; import org.geysermc.geyser.api.util.TriState; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.geyser.registry.type.CustomSkull; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.GeyserSession; @@ -176,12 +175,12 @@ public abstract class ItemTranslator { builder.blockRuntimeId(bedrockItem.getBedrockBlockId()); } - translateCustomItem(nbt, builder, bedrockItem); - if (bedrockItem == session.getItemMappings().getStoredItems().playerHead()) { translatePlayerHead(session, nbt, builder); } + translateCustomItem(nbt, builder, bedrockItem); + if (nbt != null) { // Translate the canDestroy and canPlaceOn Java NBT ListTag canDestroy = nbt.get("CanDestroy"); @@ -547,6 +546,7 @@ public abstract class ItemTranslator { int bedrockId = getCustomItem(nbt, mapping); if (bedrockId != -1) { builder.id(bedrockId); + builder.blockRuntimeId(0); } } @@ -563,13 +563,14 @@ public abstract class ItemTranslator { } String skinHash = data.skinUrl().substring(data.skinUrl().lastIndexOf('/') + 1); - GeyserConvertSkullInventoryEvent skullInventoryEvent = new GeyserConvertSkullInventoryEvent(skinHash); - GeyserImpl.getInstance().getEventBus().fire(skullInventoryEvent); - CustomBlockData replacementBlock = skullInventoryEvent.replacementBlock(); - if (replacementBlock != null) { - int bedrockId = session.getItemMappings().getCustomBlockItemIds().getInt(replacementBlock); - builder.id(bedrockId); + 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()); + + builder.id(itemId); + builder.blockRuntimeId(blockRuntimeId); } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java index 6c03a737c..58ed9c630 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/SkullBlockEntityTranslator.java @@ -31,14 +31,13 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.event.world.GeyserConvertSkullEvent; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.skin.SkinManager; +import org.geysermc.geyser.session.cache.SkullCache; import org.geysermc.geyser.skin.SkinProvider; -import java.io.IOException; import java.util.LinkedHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -75,63 +74,46 @@ public class SkullBlockEntityTranslator extends BlockEntityTranslator implements } public static int translateSkull(GeyserSession session, CompoundTag tag, Vector3i blockPosition, int blockState) { - try { - String texturesProperty = getTextures(tag).get(); + CompletableFuture texturesFuture = getTextures(tag); + if (texturesFuture.isDone()) { + try { + SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, texturesFuture.get(), blockState); + return skull.getCustomRuntimeId(); + } catch (InterruptedException | ExecutionException e) { + session.getGeyser().getLogger().debug("Failed to acquire textures for custom skull: " + blockPosition + " " + tag); + if (GeyserImpl.getInstance().getConfig().isDebugMode()) { + e.printStackTrace(); + } + } + return -1; + } + + // SkullOwner contained a username, so we have to wait for it to be retrieved + texturesFuture.whenComplete((texturesProperty, throwable) -> { if (texturesProperty == null) { session.getGeyser().getLogger().debug("Custom skull with invalid SkullOwner tag: " + blockPosition + " " + tag); - return -1; + return; } - int runtimeId = translateCustomSkull(session, blockPosition, texturesProperty, blockState); - if (runtimeId == -1) { - session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); + if (session.getEventLoop().inEventLoop()) { + putSkull(session, blockPosition, texturesProperty, blockState); } else { - session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState, runtimeId); + session.executeInEventLoop(() -> putSkull(session, blockPosition, texturesProperty, blockState)); } - return runtimeId; - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - public static int translateCustomSkull(GeyserSession session, Vector3i blockPosition, String texturesProperty, int blockState) { - try { - SkinManager.GameProfileData gameProfileData = SkinManager.GameProfileData.loadFromJson(texturesProperty); - if (gameProfileData == null || gameProfileData.skinUrl() == null) { - session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + blockPosition + " Textures: " + texturesProperty); - return -1; - } - - String skinUrl = gameProfileData.skinUrl(); - String skinHash = skinUrl.substring(skinUrl.lastIndexOf('/') + 1); - - byte floorRotation = BlockStateValues.getSkullRotation(blockState); - GeyserConvertSkullEvent.WallDirection wallDirection = GeyserConvertSkullEvent.WallDirection.INVALID; - boolean onFloor = true; - if (floorRotation == -1) { - // Wall skull - onFloor = false; - int wallRotation = BlockStateValues.getSkullWallDirections().get(blockState); - wallDirection = switch (wallRotation) { - case 0 -> GeyserConvertSkullEvent.WallDirection.SOUTH; - case 90 -> GeyserConvertSkullEvent.WallDirection.WEST; - case 180 -> GeyserConvertSkullEvent.WallDirection.NORTH; - case 270 -> GeyserConvertSkullEvent.WallDirection.EAST; - default -> GeyserConvertSkullEvent.WallDirection.INVALID; - }; - } - GeyserConvertSkullEvent event = new GeyserConvertSkullEvent(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), onFloor, wallDirection, floorRotation, skinHash); - GeyserImpl.getInstance().getEventBus().fire(event); - - if (event.getNewBlockState() != null) { - return session.getBlockMappings().getCustomBlockStateIds().getOrDefault(event.getNewBlockState(), -1); - } - - if (event.isCancelled()) { - return -1; - } - } catch (IOException e) { - session.getGeyser().getLogger().debug("Player skull with invalid Skin tag: " + blockPosition + " Textures: " + texturesProperty + " Message: " + e.getMessage()); - } + }); + // We don't have the textures yet, so we can't determine if a custom block was defined for this skull return -1; } + + private static void putSkull(GeyserSession session, Vector3i blockPosition, String texturesProperty, int blockState) { + SkullCache.Skull skull = session.getSkullCache().putSkull(blockPosition, texturesProperty, blockState); + if (skull.getCustomRuntimeId() != -1) { + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.setDataLayer(0); + updateBlockPacket.setBlockPosition(blockPosition); + updateBlockPacket.setRuntimeId(skull.getCustomRuntimeId()); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); + updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); + session.sendUpstreamPacket(updateBlockPacket); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java index 248aaba26..254357a1e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java @@ -64,11 +64,11 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator