package org.bukkit.craftbukkit.util; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.io.Files; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Dynamic; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import net.minecraft.SharedConstants; import net.minecraft.advancements.critereon.LootDeserializationContext; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.DynamicOpsNBT; import net.minecraft.nbt.MojangsonParser; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagString; import net.minecraft.resources.MinecraftKey; import net.minecraft.server.AdvancementDataWorld; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ChatDeserializer; import net.minecraft.util.datafix.DataConverterRegistry; import net.minecraft.util.datafix.fixes.DataConverterTypes; import net.minecraft.world.entity.ai.attributes.AttributeBase; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.material.FluidType; import net.minecraft.world.level.storage.SavedFile; import org.bukkit.Bukkit; import org.bukkit.Fluid; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.UnsafeValues; import org.bukkit.advancement.Advancement; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeModifier; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftEquipmentSlot; import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; import org.bukkit.craftbukkit.attribute.CraftAttributeMap; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.legacy.CraftLegacy; import org.bukkit.inventory.CreativeCategory; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.PluginDescriptionFile; @SuppressWarnings("deprecation") public final class CraftMagicNumbers implements UnsafeValues { public static final UnsafeValues INSTANCE = new CraftMagicNumbers(); private CraftMagicNumbers() {} public static IBlockData getBlock(MaterialData material) { return getBlock(material.getItemType(), material.getData()); } public static IBlockData getBlock(Material material, byte data) { return CraftLegacy.fromLegacyData(CraftLegacy.toLegacy(material), data); } public static MaterialData getMaterial(IBlockData data) { return CraftLegacy.toLegacy(getMaterial(data.getBlock())).getNewData(toLegacyData(data)); } public static Item getItem(Material material, short data) { if (material.isLegacy()) { return CraftLegacy.fromLegacyData(CraftLegacy.toLegacy(material), data); } return getItem(material); } public static MaterialData getMaterialData(Item item) { return CraftLegacy.toLegacyData(getMaterial(item)); } // ======================================================================== private static final Map BLOCK_MATERIAL = new HashMap<>(); private static final Map ITEM_MATERIAL = new HashMap<>(); private static final Map FLUID_MATERIAL = new HashMap<>(); private static final Map MATERIAL_ITEM = new HashMap<>(); private static final Map MATERIAL_BLOCK = new HashMap<>(); private static final Map MATERIAL_FLUID = new HashMap<>(); static { for (Block block : BuiltInRegistries.BLOCK) { BLOCK_MATERIAL.put(block, Material.getMaterial(BuiltInRegistries.BLOCK.getKey(block).getPath().toUpperCase(Locale.ROOT))); } for (Item item : BuiltInRegistries.ITEM) { ITEM_MATERIAL.put(item, Material.getMaterial(BuiltInRegistries.ITEM.getKey(item).getPath().toUpperCase(Locale.ROOT))); } for (FluidType fluid : BuiltInRegistries.FLUID) { FLUID_MATERIAL.put(fluid, Registry.FLUID.get(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.FLUID.getKey(fluid)))); } for (Material material : Material.values()) { if (material.isLegacy()) { continue; } MinecraftKey key = key(material); BuiltInRegistries.ITEM.getOptional(key).ifPresent((item) -> { MATERIAL_ITEM.put(material, item); }); BuiltInRegistries.BLOCK.getOptional(key).ifPresent((block) -> { MATERIAL_BLOCK.put(material, block); }); BuiltInRegistries.FLUID.getOptional(key).ifPresent((fluid) -> { MATERIAL_FLUID.put(material, fluid); }); } } public static Material getMaterial(Block block) { return BLOCK_MATERIAL.get(block); } public static Material getMaterial(Item item) { return ITEM_MATERIAL.getOrDefault(item, Material.AIR); } public static Fluid getFluid(FluidType fluid) { return FLUID_MATERIAL.get(fluid); } public static Item getItem(Material material) { if (material != null && material.isLegacy()) { material = CraftLegacy.fromLegacy(material); } return MATERIAL_ITEM.get(material); } public static Block getBlock(Material material) { if (material != null && material.isLegacy()) { material = CraftLegacy.fromLegacy(material); } return MATERIAL_BLOCK.get(material); } public static FluidType getFluid(Fluid fluid) { return MATERIAL_FLUID.get(fluid); } public static MinecraftKey key(Material mat) { return CraftNamespacedKey.toMinecraft(mat.getKey()); } // ======================================================================== public static byte toLegacyData(IBlockData data) { return CraftLegacy.toLegacyData(data); } @Override public Material toLegacy(Material material) { return CraftLegacy.toLegacy(material); } @Override public Material fromLegacy(Material material) { return CraftLegacy.fromLegacy(material); } @Override public Material fromLegacy(MaterialData material) { return CraftLegacy.fromLegacy(material); } @Override public Material fromLegacy(MaterialData material, boolean itemPriority) { return CraftLegacy.fromLegacy(material, itemPriority); } @Override public BlockData fromLegacy(Material material, byte data) { return CraftBlockData.fromData(getBlock(material, data)); } @Override public Material getMaterial(String material, int version) { Preconditions.checkArgument(material != null, "material == null"); Preconditions.checkArgument(version <= this.getDataVersion(), "Newer version! Server downgrades are not supported!"); // Fastpath up to date materials if (version == this.getDataVersion()) { return Material.getMaterial(material); } Dynamic name = new Dynamic<>(DynamicOpsNBT.INSTANCE, NBTTagString.valueOf("minecraft:" + material.toLowerCase(Locale.ROOT))); Dynamic converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.ITEM_NAME, name, version, this.getDataVersion()); if (name.equals(converted)) { converted = DataConverterRegistry.getDataFixer().update(DataConverterTypes.BLOCK_NAME, name, version, this.getDataVersion()); } return Material.matchMaterial(converted.asString("")); } /** * This string should be changed if the NMS mappings do. * * It has no meaning and should only be used as an equality check. Plugins * which are sensitive to the NMS mappings may read it and refuse to load if * it cannot be found or is different to the expected value. * * Remember: NMS is not supported API and may break at any time for any * reason irrespective of this. There is often supported API to do the same * thing as many common NMS usages. If not, you are encouraged to open a * feature and/or pull request for consideration, or use a well abstracted * third-party API such as ProtocolLib. * * @return string */ public String getMappingsVersion() { return "1afe2ffe8a9d7fc510442a168b3d4338"; } @Override public int getDataVersion() { return SharedConstants.getCurrentVersion().getWorldVersion(); } @Override public ItemStack modifyItemStack(ItemStack stack, String arguments) { net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); try { nmsStack.setTag((NBTTagCompound) MojangsonParser.parseTag(arguments)); } catch (CommandSyntaxException ex) { Logger.getLogger(CraftMagicNumbers.class.getName()).log(Level.SEVERE, null, ex); } stack.setItemMeta(CraftItemStack.getItemMeta(nmsStack)); return stack; } private static File getBukkitDataPackFolder() { return new File(MinecraftServer.getServer().getWorldPath(SavedFile.DATAPACK_DIR).toFile(), "bukkit"); } @Override public Advancement loadAdvancement(NamespacedKey key, String advancement) { if (Bukkit.getAdvancement(key) != null) { throw new IllegalArgumentException("Advancement " + key + " already exists."); } MinecraftKey minecraftkey = CraftNamespacedKey.toMinecraft(key); JsonElement jsonelement = AdvancementDataWorld.GSON.fromJson(advancement, JsonElement.class); JsonObject jsonobject = ChatDeserializer.convertToJsonObject(jsonelement, "advancement"); net.minecraft.advancements.Advancement.SerializedAdvancement nms = net.minecraft.advancements.Advancement.SerializedAdvancement.fromJson(jsonobject, new LootDeserializationContext(minecraftkey, MinecraftServer.getServer().getPredicateManager())); if (nms != null) { MinecraftServer.getServer().getAdvancements().advancements.add(Maps.newHashMap(Collections.singletonMap(minecraftkey, nms))); Advancement bukkit = Bukkit.getAdvancement(key); if (bukkit != null) { File file = new File(getBukkitDataPackFolder(), "data" + File.separator + key.getNamespace() + File.separator + "advancements" + File.separator + key.getKey() + ".json"); file.getParentFile().mkdirs(); try { Files.write(advancement, file, Charsets.UTF_8); } catch (IOException ex) { Bukkit.getLogger().log(Level.SEVERE, "Error saving advancement " + key, ex); } MinecraftServer.getServer().getPlayerList().reloadResources(); return bukkit; } } return null; } @Override public boolean removeAdvancement(NamespacedKey key) { File file = new File(getBukkitDataPackFolder(), "data" + File.separator + key.getNamespace() + File.separator + "advancements" + File.separator + key.getKey() + ".json"); return file.delete(); } private static final List SUPPORTED_API = Arrays.asList("1.13", "1.14", "1.15", "1.16", "1.17", "1.18", "1.19"); @Override public void checkSupported(PluginDescriptionFile pdf) throws InvalidPluginException { String minimumVersion = MinecraftServer.getServer().server.minimumAPI; int minimumIndex = SUPPORTED_API.indexOf(minimumVersion); if (pdf.getAPIVersion() != null) { int pluginIndex = SUPPORTED_API.indexOf(pdf.getAPIVersion()); if (pluginIndex == -1) { throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); } if (pluginIndex < minimumIndex) { throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); } } else { if (minimumIndex == -1) { CraftLegacy.init(); Bukkit.getLogger().log(Level.WARNING, "Legacy plugin " + pdf.getFullName() + " does not specify an api-version."); } else { throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); } } } public static boolean isLegacy(PluginDescriptionFile pdf) { return pdf.getAPIVersion() == null; } @Override public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) { try { clazz = Commodore.convert(clazz, !isLegacy(pdf)); } catch (Exception ex) { Bukkit.getLogger().log(Level.SEVERE, "Fatal error trying to convert " + pdf.getFullName() + ":" + path, ex); } return clazz; } @Override public Multimap getDefaultAttributeModifiers(Material material, EquipmentSlot slot) { ImmutableMultimap.Builder defaultAttributes = ImmutableMultimap.builder(); Multimap nmsDefaultAttributes = getItem(material).getDefaultAttributeModifiers(CraftEquipmentSlot.getNMS(slot)); for (Entry mapEntry : nmsDefaultAttributes.entries()) { Attribute attribute = CraftAttributeMap.fromMinecraft(BuiltInRegistries.ATTRIBUTE.getKey(mapEntry.getKey()).toString()); defaultAttributes.put(attribute, CraftAttributeInstance.convert(mapEntry.getValue(), slot)); } return defaultAttributes.build(); } @Override public CreativeCategory getCreativeCategory(Material material) { return CreativeCategory.BUILDING_BLOCKS; // TODO: Figure out what to do with this } /** * This helper class represents the different NBT Tags. *

* These should match NBTBase#getTypeId */ public static class NBT { public static final int TAG_END = 0; public static final int TAG_BYTE = 1; public static final int TAG_SHORT = 2; public static final int TAG_INT = 3; public static final int TAG_LONG = 4; public static final int TAG_FLOAT = 5; public static final int TAG_DOUBLE = 6; public static final int TAG_BYTE_ARRAY = 7; public static final int TAG_STRING = 8; public static final int TAG_LIST = 9; public static final int TAG_COMPOUND = 10; public static final int TAG_INT_ARRAY = 11; public static final int TAG_ANY_NUMBER = 99; } }