3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-12-26 00:00:41 +01:00

Remove skull translation events and define custom blocks for custom skulls

Clean up skull block translation a bit
Dieser Commit ist enthalten in:
davchoo 2022-07-07 16:18:24 -04:00
Ursprung f553dee3a5
Commit 49f7f6d2f9
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: A0168C8E45799B7D
15 geänderte Dateien mit 325 neuen und 301 gelöschten Zeilen

Datei anzeigen

@ -38,6 +38,8 @@ import java.util.Map;
public interface CustomBlockData {
@NonNull String name();
@NonNull String identifier();
CustomBlockComponents components();
@NonNull Map<String, CustomBlockProperty<?>> properties();

Datei anzeigen

@ -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) {
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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<CustomBlockData> 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<String, CustomSkull> CUSTOM_SKULLS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new));
static {
BlockRegistryPopulator.populate();
}

Datei anzeigen

@ -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<NbtMap> blockStates, List<CustomBlockState> 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() {

Datei anzeigen

@ -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);

Datei anzeigen

@ -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<CustomBlockPermutation> 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<CustomBlockPermutation> 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<CustomBlockPermutation> 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<CustomBlockPermutation> 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());
}
}
}

Datei anzeigen

@ -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));

Datei anzeigen

@ -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;

Datei anzeigen

@ -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);
}
}
}

Datei anzeigen

@ -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<String> 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);
}
}
}

Datei anzeigen

@ -64,11 +64,11 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(type, position.getX(), position.getY(), position.getZ(),
packet.getNbt(), blockState), packet.getPosition());
// Check for custom skulls.
boolean customSkull = false;
boolean hasCustomHeadBlock = false;
if (session.getPreferencesCache().showCustomSkulls() && packet.getNbt() != null && packet.getNbt().contains("SkullOwner")) {
int runtimeId = SkullBlockEntityTranslator.translateSkull(session, packet.getNbt(), position, blockState);
if (runtimeId != -1) {
customSkull = true;
hasCustomHeadBlock = true;
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(position);
@ -78,7 +78,7 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator<ClientboundB
session.sendUpstreamPacket(updateBlockPacket);
}
}
if (!customSkull) {
if (!hasCustomHeadBlock) {
BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(type, position.getX(), position.getY(), position.getZ(),
packet.getNbt(), blockState), packet.getPosition());
}

Datei anzeigen

@ -43,6 +43,7 @@ import org.geysermc.geyser.level.chunk.GeyserChunkSection;
import org.geysermc.geyser.level.chunk.bitarray.SingletonBitArray;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.level.block.entity.BedrockOnlyBlockEntity;
@ -141,9 +142,10 @@ public class ChunkUtils {
// Skull is gone
session.getSkullCache().removeSkull(position);
} else if (skullVariant == 3) {
int customRuntimeId = session.getSkullCache().updateSkull(position, blockState);
if (customRuntimeId != -1) {
blockId = customRuntimeId;
// The changed block was a player skull so check if a custom block was defined for this skull
SkullCache.Skull skull = session.getSkullCache().updateSkull(position, blockState);
if (skull != null && skull.getCustomRuntimeId() != -1) {
blockId = skull.getCustomRuntimeId();
}
}