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:
Ursprung
f553dee3a5
Commit
49f7f6d2f9
@ -38,6 +38,8 @@ import java.util.Map;
|
||||
public interface CustomBlockData {
|
||||
@NonNull String name();
|
||||
|
||||
@NonNull String identifier();
|
||||
|
||||
CustomBlockComponents components();
|
||||
|
||||
@NonNull Map<String, CustomBlockProperty<?>> properties();
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
171
core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java
Normale Datei
171
core/src/main/java/org/geysermc/geyser/registry/type/CustomSkull.java
Normale Datei
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren