/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * 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. */ package org.lanternpowered.server.data.io.store.item; import static org.lanternpowered.server.data.util.DataUtil.getOrCreateView; import static org.lanternpowered.server.text.translation.TranslationHelper.t; import org.lanternpowered.server.data.io.store.ObjectSerializer; import org.lanternpowered.server.data.io.store.SimpleValueContainer; import org.lanternpowered.server.data.io.store.data.DataHolderStore; import org.lanternpowered.server.game.Lantern; import org.lanternpowered.server.game.registry.type.block.BlockRegistryModule; import org.lanternpowered.server.game.registry.type.data.CoalTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.CookedFishRegistryModule; import org.lanternpowered.server.game.registry.type.data.DirtTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.DyeColorRegistryModule; import org.lanternpowered.server.game.registry.type.data.FishRegistryModule; import org.lanternpowered.server.game.registry.type.data.GoldenAppleRegistryModule; import org.lanternpowered.server.game.registry.type.data.PlantTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.SandTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.SandstoneTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.ShrubTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.SkullTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.SlabTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.StoneTypeRegistryModule; import org.lanternpowered.server.game.registry.type.data.TreeTypeRegistryModule; import org.lanternpowered.server.game.registry.type.item.EnchantmentRegistryModule; import org.lanternpowered.server.game.registry.type.item.ItemRegistryModule; import org.lanternpowered.server.inventory.LanternItemStack; import org.lanternpowered.server.item.enchantment.LanternEnchantment; import org.lanternpowered.server.text.LanternTexts; import org.spongepowered.api.CatalogType; import org.spongepowered.api.block.BlockType; import org.spongepowered.api.block.BlockTypes; import org.spongepowered.api.data.DataContainer; import org.spongepowered.api.data.DataQuery; import org.spongepowered.api.data.DataView; import org.spongepowered.api.data.MemoryDataContainer; import org.spongepowered.api.data.key.Key; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.data.meta.ItemEnchantment; import org.spongepowered.api.data.persistence.InvalidDataException; import org.spongepowered.api.data.type.DyeColor; import org.spongepowered.api.data.type.SandstoneType; import org.spongepowered.api.data.type.SlabType; import org.spongepowered.api.data.type.TreeType; import org.spongepowered.api.data.value.mutable.ListValue; import org.spongepowered.api.item.Enchantment; import org.spongepowered.api.item.ItemType; import org.spongepowered.api.item.ItemTypes; import org.spongepowered.api.text.Text; import org.spongepowered.api.text.TranslatableText; import org.spongepowered.api.text.format.TextColors; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; public class ItemStackStore extends DataHolderStore<LanternItemStack> implements ObjectSerializer<LanternItemStack> { private static final DataQuery IDENTIFIER = DataQuery.of("id"); public static final DataQuery QUANTITY = DataQuery.of("Count"); public static final DataQuery DATA = DataQuery.of("Damage"); public static final DataQuery TAG = DataQuery.of("tag"); static final DataQuery DISPLAY = DataQuery.of("display"); private static final DataQuery NAME = DataQuery.of("Name"); private static final DataQuery LOCALIZED_NAME = DataQuery.of("LocName"); private static final DataQuery LORE = DataQuery.of("Lore"); private static final DataQuery UNBREAKABLE = DataQuery.of("Unbreakable"); private static final DataQuery CAN_DESTROY = DataQuery.of("CanDestroy"); private static final DataQuery ENCHANTMENTS = DataQuery.of("ench"); private static final DataQuery ENCHANTMENT_ID = DataQuery.of("id"); private static final DataQuery ENCHANTMENT_LEVEL = DataQuery.of("lvl"); private static final DataQuery STORED_ENCHANTMENTS = DataQuery.of("StoredEnchantments"); private final Map<ItemType, ItemTypeObjectSerializer> itemTypeSerializers = new HashMap<>(); { final DataValueItemTypeObjectSerializer<TreeType> treeTypeSerializer = new DataValueItemTypeObjectSerializer<>(Keys.TREE_TYPE, TreeTypeRegistryModule.get()); add(BlockTypes.LOG, treeTypeSerializer); add(BlockTypes.WOODEN_SLAB, treeTypeSerializer); add(BlockTypes.DOUBLE_WOODEN_SLAB, treeTypeSerializer); add(BlockTypes.PLANKS, treeTypeSerializer); add(BlockTypes.LEAVES, treeTypeSerializer); add(BlockTypes.SAPLING, treeTypeSerializer); final DataValueItemTypeObjectSerializer<TreeType> treeType2Serializer = new DataValueItemTypeObjectSerializer<>(Keys.TREE_TYPE, TreeTypeRegistryModule.get(), dataValue -> dataValue + 4, internalId -> internalId - 4); add(BlockTypes.LOG2, treeType2Serializer); add(BlockTypes.LEAVES2, treeType2Serializer); final DataValueItemTypeObjectSerializer<SlabType> stoneSlab1Serializer = new DataValueItemTypeObjectSerializer<>(Keys.SLAB_TYPE, SlabTypeRegistryModule.get()); add(BlockTypes.STONE_SLAB, stoneSlab1Serializer); add(BlockTypes.DOUBLE_STONE_SLAB, stoneSlab1Serializer); final DataValueItemTypeObjectSerializer<SlabType> stoneSlab2Serializer = new DataValueItemTypeObjectSerializer<>(Keys.SLAB_TYPE, SlabTypeRegistryModule.get(), dataValue -> dataValue + 8, internalId -> internalId - 8); add(BlockTypes.STONE_SLAB2, stoneSlab2Serializer); add(BlockTypes.DOUBLE_STONE_SLAB2, stoneSlab2Serializer); add(BlockTypes.QUARTZ_BLOCK, new QuartzItemTypeSerializer()); final DataValueItemTypeObjectSerializer<SandstoneType> sandstoneTypeSerializer = new DataValueItemTypeObjectSerializer<>(Keys.SANDSTONE_TYPE, SandstoneTypeRegistryModule.get()); add(BlockTypes.SANDSTONE, sandstoneTypeSerializer); add(BlockTypes.RED_SANDSTONE, sandstoneTypeSerializer); final DataValueItemTypeObjectSerializer<DyeColor> dyeColorSerializer = new DataValueItemTypeObjectSerializer<>(Keys.DYE_COLOR, DyeColorRegistryModule.get()); add(BlockTypes.WOOL, dyeColorSerializer); add(BlockTypes.STAINED_HARDENED_CLAY, dyeColorSerializer); add(BlockTypes.STAINED_GLASS, dyeColorSerializer); add(BlockTypes.STAINED_GLASS_PANE, dyeColorSerializer); final ShulkerBoxItemObjectSerializer shulkerBoxSerializer = new ShulkerBoxItemObjectSerializer(); add(BlockTypes.BLACK_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.BLUE_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.BROWN_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.CYAN_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.GRAY_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.GREEN_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.LIGHT_BLUE_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.LIME_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.MAGENTA_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.ORANGE_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.PINK_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.PURPLE_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.RED_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.SILVER_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.WHITE_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.YELLOW_SHULKER_BOX, shulkerBoxSerializer); add(BlockTypes.DIRT, new DataValueItemTypeObjectSerializer<>(Keys.DIRT_TYPE, DirtTypeRegistryModule.get())); add(BlockTypes.STONE, new DataValueItemTypeObjectSerializer<>(Keys.STONE_TYPE, StoneTypeRegistryModule.get())); add(BlockTypes.SAND, new DataValueItemTypeObjectSerializer<>(Keys.SAND_TYPE, SandTypeRegistryModule.get())); add(BlockTypes.SPONGE, new SpongeItemTypeObjectSerializer()); add(BlockTypes.TALLGRASS, new DataValueItemTypeObjectSerializer<>(Keys.SHRUB_TYPE, ShrubTypeRegistryModule.get())); add(BlockTypes.YELLOW_FLOWER, new DataValueItemTypeObjectSerializer<>(Keys.PLANT_TYPE, PlantTypeRegistryModule.get())); add(BlockTypes.RED_FLOWER, new DataValueItemTypeObjectSerializer<>(Keys.PLANT_TYPE, PlantTypeRegistryModule.get(), dataValue -> dataValue + 16, internalId -> internalId - 16)); add(ItemTypes.COAL, new DataValueItemTypeObjectSerializer<>(Keys.COAL_TYPE, CoalTypeRegistryModule.get())); add(ItemTypes.FIREWORK_CHARGE, new FireworkChargeItemTypeObjectSerializer()); add(ItemTypes.FIREWORKS, new FireworksItemTypeObjectSerializer()); add(ItemTypes.GOLDEN_APPLE, new DataValueItemTypeObjectSerializer<>(Keys.GOLDEN_APPLE_TYPE, GoldenAppleRegistryModule.get())); add(ItemTypes.FISH, new DataValueItemTypeObjectSerializer<>(Keys.FISH_TYPE, FishRegistryModule.get())); add(ItemTypes.COOKED_FISH, new DataValueItemTypeObjectSerializer<>(Keys.COOKED_FISH, CookedFishRegistryModule.get())); add(ItemTypes.DYE, new DataValueItemTypeObjectSerializer<>(Keys.DYE_COLOR, DyeColorRegistryModule.get(), dataValue -> 15 - dataValue, internalId -> 15 - internalId)); add(ItemTypes.BANNER, new DataValueItemTypeObjectSerializer<>(Keys.DYE_COLOR, DyeColorRegistryModule.get(), dataValue -> 15 - dataValue, internalId -> 15 - internalId)); add(ItemTypes.SKULL, new DataValueItemTypeObjectSerializer<>(Keys.SKULL_TYPE, SkullTypeRegistryModule.get())); add(ItemTypes.WRITABLE_BOOK, new WritableBookItemTypeObjectSerializer()); add(ItemTypes.WRITTEN_BOOK, new WrittenBookItemTypeObjectSerializer()); final ColoredLeatherItemTypeObjectSerializer leatherSerializer = new ColoredLeatherItemTypeObjectSerializer(); add(ItemTypes.LEATHER_BOOTS, leatherSerializer); add(ItemTypes.LEATHER_CHESTPLATE, leatherSerializer); add(ItemTypes.LEATHER_HELMET, leatherSerializer); add(ItemTypes.LEATHER_LEGGINGS, leatherSerializer); final PotionEffectsItemTypeObjectSerializer potionEffectsSerializer = new PotionEffectsItemTypeObjectSerializer(); add(ItemTypes.POTION, potionEffectsSerializer); add(ItemTypes.SPLASH_POTION, potionEffectsSerializer); add(ItemTypes.LINGERING_POTION, potionEffectsSerializer); add(ItemTypes.TIPPED_ARROW, potionEffectsSerializer); } private void add(ItemType itemType, ItemTypeObjectSerializer serializer) { this.itemTypeSerializers.put(itemType, serializer); } private void add(BlockType blockType, ItemTypeObjectSerializer serializer) { this.itemTypeSerializers.put(blockType.getItem().get(), serializer); } @Override public LanternItemStack deserialize(DataView dataView) throws InvalidDataException { String identifier = dataView.getString(IDENTIFIER).get(); // Fix a identifier type in the mc server if (identifier.equals("minecraft:cooked_fished")) { identifier = "minecraft:cooked_fish"; } else if (identifier.equals("minecraft:totem")) { identifier = "minecraft:totem_of_undying"; } final String identifier1 = identifier; final ItemType itemType = ItemRegistryModule.get().getById(identifier).orElseThrow( () -> new InvalidDataException("There is no item type with the id: " + identifier1)); final LanternItemStack itemStack = new LanternItemStack(itemType); deserialize(itemStack, dataView); return itemStack; } @Override public DataView serialize(LanternItemStack object) { final DataContainer dataContainer = new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED); dataContainer.set(IDENTIFIER, object.getItem().getId()); serialize(object, dataContainer); return dataContainer; } @Override public void deserialize(LanternItemStack object, DataView dataView) { object.setQuantity(dataView.getInt(QUANTITY).get()); // All the extra data we will handle will be stored in the tag final DataView tag = dataView.getView(TAG).orElseGet(() -> new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED)); tag.set(ItemTypeObjectSerializer.DATA_VALUE, dataView.getShort(DATA).get()); super.deserialize(object, tag); } @Override public void serialize(LanternItemStack object, DataView dataView) { dataView.set(QUANTITY, (byte) object.getQuantity()); final DataView tag = dataView.createView(TAG); super.serialize(object, tag); dataView.set(DATA, tag.getShort(ItemTypeObjectSerializer.DATA_VALUE).orElse((short) 0)); tag.remove(ItemTypeObjectSerializer.DATA_VALUE); if (tag.isEmpty()) { dataView.remove(TAG); } } @Override public void serializeValues(LanternItemStack object, SimpleValueContainer valueContainer, DataView dataView) { final ItemTypeObjectSerializer serializer = this.itemTypeSerializers.get(object.getItem()); if (serializer != null) { serializer.serializeValues(object, valueContainer, dataView); } DataView displayView = null; final Optional<Text> optDisplayName = valueContainer.remove(Keys.DISPLAY_NAME); if (optDisplayName.isPresent()) { displayView = getOrCreateView(dataView, DISPLAY); final Text displayName = optDisplayName.get(); if (displayName instanceof TranslatableText) { final TranslatableText name1 = (TranslatableText) displayName; // We can only do this for translatable text without any formatting if (name1.getArguments().isEmpty() && name1.getChildren().isEmpty() && name1.getStyle().isEmpty() && name1.getColor() == TextColors.NONE) { displayView.set(LOCALIZED_NAME, name1.getTranslation().getId()); } else { displayView.set(NAME, LanternTexts.toLegacy(displayName)); } } else { displayView.set(NAME, LanternTexts.toLegacy(displayName)); } } final Optional<List<Text>> optLore = valueContainer.remove(Keys.ITEM_LORE); if (optLore.isPresent() && !optLore.get().isEmpty()) { if (displayView == null) { displayView = getOrCreateView(dataView, DISPLAY); } displayView.set(LORE, optLore.get().stream().map(LanternTexts::toLegacy).collect(Collectors.toList())); } if (valueContainer.remove(Keys.UNBREAKABLE).orElse(false)) { dataView.set(UNBREAKABLE, (byte) 1); } final Optional<Set<BlockType>> optBlockTypes = valueContainer.remove(Keys.BREAKABLE_BLOCK_TYPES); if (optBlockTypes.isPresent() && !optBlockTypes.get().isEmpty()) { dataView.set(CAN_DESTROY, optBlockTypes.get().stream().map(CatalogType::getId).collect(Collectors.toSet())); } valueContainer.remove(Keys.ITEM_ENCHANTMENTS).ifPresent(list -> serializeEnchantments(dataView, ENCHANTMENTS, list)); valueContainer.remove(Keys.STORED_ENCHANTMENTS).ifPresent(list -> serializeEnchantments(dataView, STORED_ENCHANTMENTS, list)); super.serializeValues(object, valueContainer, dataView); } @Override public void deserializeValues(LanternItemStack object, SimpleValueContainer valueContainer, DataView dataView) { final ItemTypeObjectSerializer serializer = this.itemTypeSerializers.get(object.getItem()); if (serializer != null) { serializer.deserializeValues(object, valueContainer, dataView); } final Optional<DataView> optDisplayView = dataView.getView(DISPLAY); if (optDisplayView.isPresent()) { final DataView displayView = optDisplayView.get(); if (!valueContainer.get(Keys.DISPLAY_NAME).isPresent()) { Optional<String> name = displayView.getString(NAME); if (name.isPresent()) { valueContainer.set(Keys.DISPLAY_NAME, LanternTexts.fromLegacy(name.get())); } else if ((name = displayView.getString(LOCALIZED_NAME)).isPresent()) { valueContainer.set(Keys.DISPLAY_NAME, t(name.get())); } } dataView.getStringList(LORE).ifPresent(lore -> { if (!lore.isEmpty()) { valueContainer.set(Keys.ITEM_LORE, lore.stream().map(LanternTexts::fromLegacy).collect(Collectors.toList())); } }); } dataView.getStringList(CAN_DESTROY).ifPresent(types -> { if (!types.isEmpty()) { final Set<BlockType> blockTypes = new HashSet<>(); types.forEach(type -> BlockRegistryModule.get().getById(type).ifPresent(blockTypes::add)); valueContainer.set(Keys.BREAKABLE_BLOCK_TYPES, blockTypes); } }); deserializeEnchantments(dataView, ENCHANTMENTS, Keys.ITEM_ENCHANTMENTS, valueContainer); deserializeEnchantments(dataView, STORED_ENCHANTMENTS, Keys.STORED_ENCHANTMENTS, valueContainer); super.deserializeValues(object, valueContainer, dataView); } private void serializeEnchantments(DataView dataView, DataQuery query, List<ItemEnchantment> enchantments) { if (enchantments.isEmpty()) { return; } final List<DataView> dataViews = new ArrayList<>(); for (ItemEnchantment enchantment : enchantments) { final DataView enchantmentView = new MemoryDataContainer(DataView.SafetyMode.NO_DATA_CLONED); enchantmentView.set(ENCHANTMENT_ID, (short) ((LanternEnchantment) enchantment.getEnchantment()).getInternalId()); enchantmentView.set(ENCHANTMENT_LEVEL, (short) enchantment.getLevel()); dataViews.add(enchantmentView); } dataView.set(query, dataViews); } private void deserializeEnchantments(DataView dataView, DataQuery query, Key<ListValue<ItemEnchantment>> key, SimpleValueContainer valueContainer) { dataView.getViewList(query).ifPresent(views -> { if (!views.isEmpty()) { final List<ItemEnchantment> itemEnchantments = new ArrayList<>(); views.forEach(view -> { final Optional<Enchantment> enchantment = EnchantmentRegistryModule.get().getByInternalId(view.getInt(ENCHANTMENT_ID).get()); if (enchantment.isPresent()) { final int level = view.getInt(ENCHANTMENT_LEVEL).get(); itemEnchantments.add(new ItemEnchantment(enchantment.get(), level)); } else { Lantern.getLogger().warn("Attempted to deserialize a enchantment with unknown id: {}", view.getInt(ENCHANTMENT_ID).get()); } }); valueContainer.set(key, itemEnchantments); } }); } }