mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Finish ItemStackParser, clean up vault translator, finish implementing shelves
This commit is contained in:
@@ -25,11 +25,22 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.item.hashing.data;
|
package org.geysermc.geyser.item.hashing.data;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
// Ordered and named by Java ID
|
// Ordered and named by Java ID
|
||||||
public enum FireworkExplosionShape {
|
public enum FireworkExplosionShape {
|
||||||
SMALL_BALL,
|
SMALL_BALL,
|
||||||
LARGE_BALL,
|
LARGE_BALL,
|
||||||
STAR,
|
STAR,
|
||||||
CREEPER,
|
CREEPER,
|
||||||
BURST
|
BURST;
|
||||||
|
|
||||||
|
public static FireworkExplosionShape fromJavaIdentifier(String identifier) {
|
||||||
|
for (FireworkExplosionShape shape : values()) {
|
||||||
|
if (shape.name().toLowerCase(Locale.ROOT).equals(identifier)) {
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,35 +25,60 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.item.parser;
|
package org.geysermc.geyser.item.parser;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||||
|
import net.kyori.adventure.key.Key;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.inventory.item.DyeColor;
|
import org.geysermc.geyser.inventory.item.DyeColor;
|
||||||
import org.geysermc.geyser.inventory.item.Potion;
|
import org.geysermc.geyser.inventory.item.Potion;
|
||||||
import org.geysermc.geyser.item.Items;
|
import org.geysermc.geyser.item.Items;
|
||||||
|
import org.geysermc.geyser.item.hashing.data.FireworkExplosionShape;
|
||||||
import org.geysermc.geyser.item.type.Item;
|
import org.geysermc.geyser.item.type.Item;
|
||||||
import org.geysermc.geyser.registry.Registries;
|
import org.geysermc.geyser.registry.Registries;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||||
|
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
||||||
|
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||||
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
|
import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTranslator;
|
||||||
import org.geysermc.geyser.util.MinecraftKey;
|
import org.geysermc.geyser.util.MinecraftKey;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.CustomModelData;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Fireworks;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to parse an item stack, or a data component patch, from NBT data.
|
||||||
|
*
|
||||||
|
* <p>This class does <em>NOT</em> parse all possible data components in a data component patch, only those that
|
||||||
|
* can visually change the way an item looks. This class should/is usually used for parsing block entity NBT data,
|
||||||
|
* such as for vault or shelf block entities.</p>
|
||||||
|
*
|
||||||
|
* <p>Be sure to update this class for Java updates!</p>
|
||||||
|
*/
|
||||||
|
// Lots of unchecked casting happens here. It should all be handled properly.
|
||||||
|
// TODO only log somethings once (like was done in vault translator)
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class ItemStackParser {
|
public class ItemStackParser {
|
||||||
private static final ItemStack AIR = new ItemStack(Items.AIR_ID);
|
|
||||||
private static final Map<DataComponentType<?>, DataComponentParser<?, ?>> PARSERS = new Reference2ObjectOpenHashMap<>();
|
private static final Map<DataComponentType<?>, DataComponentParser<?, ?>> PARSERS = new Reference2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
// We need the rawClass parameter here because the Raw type can't be inferred from the parser alone
|
||||||
private static <Raw, Parsed> void register(DataComponentType<Parsed> component, Class<Raw> rawClass, DataComponentParser<Raw, Parsed> parser) {
|
private static <Raw, Parsed> void register(DataComponentType<Parsed> component, Class<Raw> rawClass, DataComponentParser<Raw, Parsed> parser) {
|
||||||
if (PARSERS.containsKey(component)) {
|
if (PARSERS.containsKey(component)) {
|
||||||
throw new IllegalStateException("Duplicate data component parser registered for " + component);
|
throw new IllegalStateException("Duplicate data component parser registered for " + component);
|
||||||
@@ -69,41 +94,96 @@ public class ItemStackParser {
|
|||||||
registerSimple(component, parsedClass, Function.identity());
|
registerSimple(component, parsedClass, Function.identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <Parsed> void registerInstance(DataComponentType<Parsed> component, Parsed instance) {
|
private static int javaItemIdentifierToNetworkId(String identifier) {
|
||||||
register(component, Object.class, (session, o) -> instance);
|
if (identifier == null || identifier.isEmpty()) {
|
||||||
|
return Items.AIR_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item item = Registries.JAVA_ITEM_IDENTIFIERS.get(identifier);
|
||||||
|
if (item == null) {
|
||||||
|
GeyserImpl.getInstance().getLogger().warning("Received unknown item ID " + identifier + " whilst parsing NBT item stack!");
|
||||||
|
return Items.AIR_ID;
|
||||||
|
}
|
||||||
|
return item.javaId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemEnchantments parseEnchantments(GeyserSession session, NbtMap map) {
|
||||||
|
Int2IntMap enchantments = new Int2IntOpenHashMap(map.size());
|
||||||
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
|
enchantments.put(JavaRegistries.ENCHANTMENT.networkId(session, MinecraftKey.key(entry.getKey())), (int) entry.getValue());
|
||||||
|
}
|
||||||
|
return new ItemEnchantments(enchantments);
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// TODO check this again
|
// The various ignored null-warnings are for things that should never be null as they shouldn't be missing from the data component
|
||||||
// banner patterns []
|
// If they are null an exception will be thrown, but this will be caught with an error message logged
|
||||||
// base color [X]
|
|
||||||
// charged projectiles [X]
|
register(DataComponentTypes.BANNER_PATTERNS, List.class, (session, raw) -> {
|
||||||
// custom model data []
|
List<NbtMap> casted = (List<NbtMap>) raw;
|
||||||
// dyed color [X]
|
List<BannerPatternLayer> layers = new ArrayList<>();
|
||||||
// enchantment glint override [X]
|
for (NbtMap layer : casted) {
|
||||||
// enchantments [X]
|
DyeColor colour = DyeColor.getByJavaIdentifier(layer.getString("color"));
|
||||||
// firework explosion []
|
|
||||||
// item model [X]
|
// Patterns can be an ID or inline
|
||||||
// map color [X]
|
Object pattern = layer.get("pattern");
|
||||||
// pot decorations []
|
Holder<BannerPatternLayer.BannerPattern> patternHolder;
|
||||||
// profile [X]
|
if (pattern instanceof String id) {
|
||||||
|
patternHolder = Holder.ofId(JavaRegistries.BANNER_PATTERN.networkId(session, MinecraftKey.key(id)));
|
||||||
|
} else {
|
||||||
|
NbtMap inline = (NbtMap) pattern;
|
||||||
|
Key assetId = MinecraftKey.key(inline.getString("asset_id"));
|
||||||
|
String translationKey = inline.getString("translation_key");
|
||||||
|
patternHolder = Holder.ofCustom(new BannerPatternLayer.BannerPattern(assetId, translationKey));
|
||||||
|
}
|
||||||
|
layers.add(new BannerPatternLayer(patternHolder, colour.ordinal()));
|
||||||
|
}
|
||||||
|
return layers;
|
||||||
|
});
|
||||||
registerSimple(DataComponentTypes.BASE_COLOR, String.class, raw -> DyeColor.getByJavaIdentifier(raw).ordinal());
|
registerSimple(DataComponentTypes.BASE_COLOR, String.class, raw -> DyeColor.getByJavaIdentifier(raw).ordinal());
|
||||||
register(DataComponentTypes.CHARGED_PROJECTILES, List.class,
|
register(DataComponentTypes.CHARGED_PROJECTILES, List.class,
|
||||||
(session, projectiles) -> projectiles.stream()
|
(session, projectiles) -> projectiles.stream()
|
||||||
.map(object -> parseItemStack(session, (NbtMap) object))
|
.map(object -> parseItemStack(session, (NbtMap) object))
|
||||||
.toList());
|
.toList());
|
||||||
|
registerSimple(DataComponentTypes.CUSTOM_MODEL_DATA, NbtMap.class, raw -> {
|
||||||
|
List<Float> floats = raw.getList("floats", NbtType.FLOAT);
|
||||||
|
List<Boolean> flags = raw.getList("flags", NbtType.BYTE).stream().map(b -> b != 0).toList();
|
||||||
|
List<String> strings = raw.getList("strings", NbtType.STRING);
|
||||||
|
List<Integer> colours = raw.getList("colors", NbtType.INT);
|
||||||
|
return new CustomModelData(floats, flags, strings, colours);
|
||||||
|
});
|
||||||
registerSimple(DataComponentTypes.DYED_COLOR, Integer.class);
|
registerSimple(DataComponentTypes.DYED_COLOR, Integer.class);
|
||||||
registerSimple(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, Boolean.class);
|
registerSimple(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, Boolean.class);
|
||||||
registerInstance(DataComponentTypes.ENCHANTMENTS, new ItemEnchantments(new Int2IntOpenHashMap()));
|
register(DataComponentTypes.ENCHANTMENTS, NbtMap.class, ItemStackParser::parseEnchantments);
|
||||||
|
registerSimple(DataComponentTypes.FIREWORK_EXPLOSION, NbtMap.class, raw -> {
|
||||||
|
FireworkExplosionShape shape = FireworkExplosionShape.fromJavaIdentifier(raw.getString("shape"));
|
||||||
|
List<Integer> colours = raw.getList("colors", NbtType.INT);
|
||||||
|
List<Integer> fadeColours = raw.getList("fade_colors", NbtType.INT);
|
||||||
|
boolean hasTrail = raw.getBoolean("has_trail");
|
||||||
|
boolean hasTwinkle = raw.getBoolean("has_twinkle");
|
||||||
|
return new Fireworks.FireworkExplosion(shape.ordinal(),
|
||||||
|
colours.stream()
|
||||||
|
.mapToInt(i -> i) // We need to do this because MCPL wants an int[] array
|
||||||
|
.toArray(),
|
||||||
|
fadeColours.stream()
|
||||||
|
.mapToInt(i -> i)
|
||||||
|
.toArray(),
|
||||||
|
hasTrail, hasTwinkle);
|
||||||
|
});
|
||||||
registerSimple(DataComponentTypes.ITEM_MODEL, String.class, MinecraftKey::key);
|
registerSimple(DataComponentTypes.ITEM_MODEL, String.class, MinecraftKey::key);
|
||||||
registerSimple(DataComponentTypes.MAP_COLOR, Integer.class);
|
registerSimple(DataComponentTypes.MAP_COLOR, Integer.class);
|
||||||
registerSimple(DataComponentTypes.PROFILE, NbtMap.class, SkullBlockEntityTranslator::parseResolvableProfile);
|
registerSimple(DataComponentTypes.POT_DECORATIONS, List.class, list -> list.stream()
|
||||||
|
.map(item -> javaItemIdentifierToNetworkId((String) item))
|
||||||
|
.toList());
|
||||||
register(DataComponentTypes.POTION_CONTENTS, NbtMap.class, (session, map) -> {
|
register(DataComponentTypes.POTION_CONTENTS, NbtMap.class, (session, map) -> {
|
||||||
// TODO
|
Potion potion = Potion.getByJavaIdentifier(map.getString("potion"));
|
||||||
return Optional.ofNullable(tag.getString("potion")).map(Potion::getByJavaIdentifier);
|
int customColour = map.getInt("custom_color", -1);
|
||||||
|
// Not reading custom effects
|
||||||
|
String customName = map.getString("custom_name", null);
|
||||||
|
return new PotionContents(potion == null ? -1 : potion.ordinal(), customColour, List.of(), customName);
|
||||||
});
|
});
|
||||||
|
registerSimple(DataComponentTypes.PROFILE, NbtMap.class, SkullBlockEntityTranslator::parseResolvableProfile);
|
||||||
|
register(DataComponentTypes.STORED_ENCHANTMENTS, NbtMap.class, ItemStackParser::parseEnchantments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <Raw, Parsed> void parseDataComponent(GeyserSession session, DataComponents patch, DataComponentType<?> type,
|
private static <Raw, Parsed> void parseDataComponent(GeyserSession session, DataComponents patch, DataComponentType<?> type,
|
||||||
@@ -148,25 +228,39 @@ public class ItemStackParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
// TODO
|
GeyserImpl.getInstance().getLogger().error("Failed to parse data component patch from NBT data!", exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemStack parseItemStack(GeyserSession session, NbtMap map) {
|
public static ItemStack parseItemStack(GeyserSession session, @Nullable NbtMap map) {
|
||||||
try {
|
if (map == null) {
|
||||||
Item item = Registries.JAVA_ITEM_IDENTIFIERS.get(map.getString("id"));
|
return new ItemStack(Items.AIR_ID);
|
||||||
if (item == null) {
|
}
|
||||||
GeyserImpl.getInstance().getLogger().warning("Unknown item " + map.getString("id") + " whilst trying to parse NBT item stack!");
|
|
||||||
return AIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
int id = item.javaId();
|
try {
|
||||||
|
int id = javaItemIdentifierToNetworkId(map.getString("id"));
|
||||||
int count = map.getInt("count");
|
int count = map.getInt("count");
|
||||||
DataComponents patch = parseDataComponentPatch(session, map);
|
DataComponents patch = parseDataComponentPatch(session, map.getCompound("components"));
|
||||||
return new ItemStack(id, count, patch);
|
return new ItemStack(id, count, patch);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
// TODO
|
GeyserImpl.getInstance().getLogger().error("Failed to parse item stack from NBT data!", exception);
|
||||||
}
|
}
|
||||||
|
return new ItemStack(Items.AIR_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand method for calling the following methods:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link ItemStackParser#parseItemStack(GeyserSession, NbtMap)}</li>
|
||||||
|
* <li>{@link ItemTranslator#translateToBedrock(GeyserSession, ItemStack)}</li>
|
||||||
|
* <li>{@link BedrockItemBuilder#itemDataToNbt(ItemData)}</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static NbtMapBuilder javaItemStackToBedrock(GeyserSession session, @Nullable NbtMap map) {
|
||||||
|
return BedrockItemBuilder.itemDataToNbt(ItemTranslator.translateToBedrock(session, parseItemStack(session, map)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
import org.cloudburstmc.nbt.NbtType;
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -142,6 +143,17 @@ public final class BedrockItemBuilder {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates item NBT to nest within NBT with name, count, damage, and tag set.
|
||||||
|
*/
|
||||||
|
public static NbtMapBuilder itemDataToNbt(ItemData data) {
|
||||||
|
NbtMapBuilder builder = BedrockItemBuilder.createItemNbt(data.getDefinition().getIdentifier(), data.getCount(), data.getDamage());
|
||||||
|
if (data.getTag() != null) {
|
||||||
|
builder.putCompound("tag", data.getTag());
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates item NBT to nest within NBT with name, count, and damage set.
|
* Creates item NBT to nest within NBT with name, count, and damage set.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,13 +27,23 @@ package org.geysermc.geyser.translator.level.block.entity;
|
|||||||
|
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
|
import org.geysermc.geyser.item.parser.ItemStackParser;
|
||||||
import org.geysermc.geyser.level.block.type.BlockState;
|
import org.geysermc.geyser.level.block.type.BlockState;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
@BlockEntity(type = BlockEntityType.SHELF)
|
||||||
public class ShelfBlockEntityTranslator extends BlockEntityTranslator {
|
public class ShelfBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
||||||
|
// We can't translate align_items_to_bottom, I think :(
|
||||||
|
bedrockNbt.putList("Items", NbtType.COMPOUND, javaNbt.getList("Items", NbtType.COMPOUND).stream()
|
||||||
|
.sorted(Comparator.comparingInt(stack -> stack.getByte("Slot")))
|
||||||
|
.map(stack -> ItemStackParser.javaItemStackToBedrock(session, stack).build())
|
||||||
|
.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,39 +25,23 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.translator.level.block.entity;
|
package org.geysermc.geyser.translator.level.block.entity;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongList;
|
import it.unimi.dsi.fastutil.longs.LongList;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||||
import org.cloudburstmc.nbt.NbtType;
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
|
||||||
import org.cloudburstmc.protocol.common.util.TriConsumer;
|
|
||||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||||
import org.geysermc.geyser.inventory.item.Potion;
|
import org.geysermc.geyser.item.parser.ItemStackParser;
|
||||||
import org.geysermc.geyser.level.block.type.BlockState;
|
import org.geysermc.geyser.level.block.type.BlockState;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
|
||||||
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
|
|
||||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
|
||||||
import org.geysermc.geyser.util.MinecraftKey;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments;
|
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
|
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@BlockEntity(type = BlockEntityType.VAULT)
|
@BlockEntity(type = BlockEntityType.VAULT)
|
||||||
public class VaultBlockEntityTranslator extends BlockEntityTranslator {
|
public class VaultBlockEntityTranslator extends BlockEntityTranslator {
|
||||||
private static boolean loggedComponentTranslationFailure = false;
|
|
||||||
|
|
||||||
// Bedrock 1.21 does not send the position nor ID in the tag.
|
// Bedrock 1.21 does not send the position nor ID in the tag.
|
||||||
@Override
|
@Override
|
||||||
@@ -72,44 +56,7 @@ public class VaultBlockEntityTranslator extends BlockEntityTranslator {
|
|||||||
@Override
|
@Override
|
||||||
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
public void translateTag(GeyserSession session, NbtMapBuilder bedrockNbt, NbtMap javaNbt, BlockState blockState) {
|
||||||
NbtMap sharedData = javaNbt.getCompound("shared_data");
|
NbtMap sharedData = javaNbt.getCompound("shared_data");
|
||||||
|
bedrockNbt.putCompound("display_item", ItemStackParser.javaItemStackToBedrock(session, sharedData.getCompound("display_item")).build());
|
||||||
NbtMap item = sharedData.getCompound("display_item");
|
|
||||||
ItemMapping mapping = session.getItemMappings().getMapping(item.getString("id"));
|
|
||||||
if (mapping == null) {
|
|
||||||
bedrockNbt.putCompound("display_item", NbtMap.builder()
|
|
||||||
.putByte("Count", (byte) 0)
|
|
||||||
.putShort("Damage", (short) 0)
|
|
||||||
.putString("Name", "")
|
|
||||||
.putByte("WasPickedUp", (byte) 0).build());
|
|
||||||
} else {
|
|
||||||
int count = item.getInt("count");
|
|
||||||
NbtMap componentsTag = item.getCompound("components");
|
|
||||||
NbtMapBuilder itemAsNbt;
|
|
||||||
if (!componentsTag.isEmpty()) {
|
|
||||||
DataComponents components = new DataComponents(new HashMap<>());
|
|
||||||
for (Map.Entry<String, Object> entry : componentsTag.entrySet()) {
|
|
||||||
var consumer = DATA_COMPONENT_DECODERS.get(entry.getKey());
|
|
||||||
if (consumer != null) {
|
|
||||||
try {
|
|
||||||
consumer.accept(session, (NbtMap) entry.getValue(), components);
|
|
||||||
} catch (RuntimeException exception) {
|
|
||||||
if (!loggedComponentTranslationFailure) {
|
|
||||||
session.getGeyser().getLogger().warning("Failed to translate vault item component data for " + entry.getKey() + "! Did the component structure change?");
|
|
||||||
loggedComponentTranslationFailure = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ItemData bedrockItem = ItemTranslator.translateToBedrock(session, mapping.getJavaItem(), mapping, count, components).build();
|
|
||||||
itemAsNbt = BedrockItemBuilder.createItemNbt(mapping, bedrockItem.getCount(), bedrockItem.getDamage());
|
|
||||||
if (bedrockItem.getTag() != null) {
|
|
||||||
itemAsNbt.putCompound("tag", bedrockItem.getTag());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
itemAsNbt = BedrockItemBuilder.createItemNbt(mapping, count, mapping.getBedrockData());
|
|
||||||
}
|
|
||||||
bedrockNbt.putCompound("display_item", itemAsNbt.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int[]> connectedPlayers = sharedData.getList("connected_players", NbtType.INT_ARRAY);
|
List<int[]> connectedPlayers = sharedData.getList("connected_players", NbtType.INT_ARRAY);
|
||||||
LongList bedrockPlayers = new LongArrayList(connectedPlayers.size());
|
LongList bedrockPlayers = new LongArrayList(connectedPlayers.size());
|
||||||
@@ -132,24 +79,8 @@ public class VaultBlockEntityTranslator extends BlockEntityTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// From ViaVersion! thank u!!
|
// From ViaVersion! thank u!!
|
||||||
|
// TODO code dup
|
||||||
private static UUID uuidFromIntArray(int[] parts) {
|
private static UUID uuidFromIntArray(int[] parts) {
|
||||||
return new UUID((long) parts[0] << 32 | (parts[1] & 0xFFFFFFFFL), (long) parts[2] << 32 | (parts[3] & 0xFFFFFFFFL));
|
return new UUID((long) parts[0] << 32 | (parts[1] & 0xFFFFFFFFL), (long) parts[2] << 32 | (parts[3] & 0xFFFFFFFFL));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This might be easier to maintain in the long run so items don't have two translate methods.
|
|
||||||
// Also, it's not out of the question that block entities get the data component treatment, likely rendering this useless.
|
|
||||||
// The goal is to just translate the basics so clients know what potion is roughly present, and that any enchantment even exists.
|
|
||||||
private static final Map<String, TriConsumer<GeyserSession, NbtMap, DataComponents>> DATA_COMPONENT_DECODERS = Map.of(
|
|
||||||
"minecraft:potion_contents", (session, tag, components) -> {
|
|
||||||
// Can only translate built-in potions, potions with custom colours don't work
|
|
||||||
Optional.ofNullable(tag.getString("potion")).map(Potion::getByJavaIdentifier)
|
|
||||||
.ifPresent(potion -> components.put(DataComponentTypes.POTION_CONTENTS, potion.toComponent()));
|
|
||||||
},
|
|
||||||
"minecraft:enchantments", (session, tag, components) -> { // Enchanted books already have glint. Translating them doesn't matter.
|
|
||||||
Int2IntMap enchantments = new Int2IntOpenHashMap(tag.size());
|
|
||||||
for (Map.Entry<String, Object> entry : tag.entrySet()) {
|
|
||||||
enchantments.put(JavaRegistries.ENCHANTMENT.networkId(session, MinecraftKey.key(entry.getKey())), (int) entry.getValue());
|
|
||||||
}
|
|
||||||
components.put(DataComponentTypes.ENCHANTMENTS, new ItemEnchantments(enchantments));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user