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.Multimap; import com.google.common.io.Files; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.Dynamic; import com.mojang.serialization.JsonOps; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import net.minecraft.SharedConstants; import net.minecraft.advancements.AdvancementHolder; import net.minecraft.commands.CommandDispatcher; import net.minecraft.commands.arguments.item.ArgumentParserItemStack; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.DynamicOpsNBT; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagString; import net.minecraft.resources.MinecraftKey; import net.minecraft.server.AdvancementDataWorld; import net.minecraft.server.MinecraftServer; import net.minecraft.util.datafix.DataConverterRegistry; import net.minecraft.util.datafix.fixes.DataConverterTypes; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.item.Item; import net.minecraft.world.item.alchemy.PotionRegistry; import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.storage.SavedFile; import org.bukkit.Bukkit; import org.bukkit.FeatureFlag; import org.bukkit.Keyed; 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.CraftFeatureFlag; import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.attribute.CraftAttribute; import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.damage.CraftDamageEffect; import org.bukkit.craftbukkit.damage.CraftDamageSourceBuilder; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.legacy.CraftLegacy; import org.bukkit.craftbukkit.legacy.FieldRename; import org.bukkit.craftbukkit.potion.CraftPotionType; import org.bukkit.damage.DamageEffect; import org.bukkit.damage.DamageSource; import org.bukkit.damage.DamageType; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; 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; import org.bukkit.potion.PotionType; @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 MATERIAL_ITEM = new HashMap<>(); private static final Map MATERIAL_BLOCK = 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 (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); }); } } 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 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 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 "ee13f98a43b9c5abffdcc0bb24154460"; } @Override public int getDataVersion() { return SharedConstants.getCurrentVersion().getDataVersion().getVersion(); } @Override public ItemStack modifyItemStack(ItemStack stack, String arguments) { net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); try { nmsStack.applyComponents(new ArgumentParserItemStack(CommandDispatcher.createValidationContext(MinecraftServer.getDefaultRegistryAccess())).parse(new StringReader(arguments)).components()); } 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) { Preconditions.checkArgument(Bukkit.getAdvancement(key) == null, "Advancement %s already exists", key); MinecraftKey minecraftkey = CraftNamespacedKey.toMinecraft(key); JsonElement jsonelement = AdvancementDataWorld.GSON.fromJson(advancement, JsonElement.class); net.minecraft.advancements.Advancement nms = net.minecraft.advancements.Advancement.CODEC.parse(JsonOps.INSTANCE, jsonelement).getOrThrow(JsonParseException::new); if (nms != null) { MinecraftServer.getServer().getAdvancements().advancements.put(minecraftkey, new AdvancementHolder(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(); } @Override public void checkSupported(PluginDescriptionFile pdf) throws InvalidPluginException { ApiVersion toCheck = ApiVersion.getOrCreateVersion(pdf.getAPIVersion()); ApiVersion minimumVersion = MinecraftServer.getServer().server.minimumAPI; if (toCheck.isNewerThan(ApiVersion.CURRENT)) { // Newer than supported throw new InvalidPluginException("Unsupported API version " + pdf.getAPIVersion()); } if (toCheck.isOlderThan(minimumVersion)) { // Older than supported throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); } if (toCheck.isOlderThan(ApiVersion.FLATTENING)) { CraftLegacy.init(); } if (toCheck == ApiVersion.NONE) { Bukkit.getLogger().log(Level.WARNING, "Legacy plugin " + pdf.getFullName() + " does not specify an api-version."); } } 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, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities); } 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(); ItemAttributeModifiers nmsDefaultAttributes = getItem(material).getDefaultAttributeModifiers(); nmsDefaultAttributes.forEach(CraftEquipmentSlot.getNMS(slot), (key, value) -> { Attribute attribute = CraftAttribute.minecraftToBukkit(key.value()); defaultAttributes.put(attribute, CraftAttributeInstance.convert(value, slot)); }); return defaultAttributes.build(); } @Override public CreativeCategory getCreativeCategory(Material material) { return CreativeCategory.BUILDING_BLOCKS; // TODO: Figure out what to do with this } @Override public String getBlockTranslationKey(Material material) { Block block = getBlock(material); return (block != null) ? block.getDescriptionId() : null; } @Override public String getItemTranslationKey(Material material) { Item item = getItem(material); return (item != null) ? item.getDescriptionId() : null; } @Override public String getTranslationKey(EntityType entityType) { Preconditions.checkArgument(entityType.getName() != null, "Invalid name of EntityType %s for translation key", entityType); return EntityTypes.byString(entityType.getName()).map(EntityTypes::getDescriptionId).orElseThrow(); } @Override public String getTranslationKey(ItemStack itemStack) { net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); return nmsItemStack.getItem().getDescriptionId(nmsItemStack); } @Override public String getTranslationKey(final Attribute attribute) { return CraftAttribute.bukkitToMinecraft(attribute).getDescriptionId(); } @Override public FeatureFlag getFeatureFlag(NamespacedKey namespacedKey) { Preconditions.checkArgument(namespacedKey != null, "NamespaceKey cannot be null"); return CraftFeatureFlag.getFromNMS(namespacedKey); } @Override public PotionType.InternalPotionData getInternalPotionData(NamespacedKey namespacedKey) { PotionRegistry potionRegistry = CraftRegistry.getMinecraftRegistry(Registries.POTION) .getOptional(CraftNamespacedKey.toMinecraft(namespacedKey)).orElseThrow(); return new CraftPotionType(namespacedKey, potionRegistry); } @Override public DamageEffect getDamageEffect(String key) { Preconditions.checkArgument(key != null, "key cannot be null"); return CraftDamageEffect.getById(key); } @Override public DamageSource.Builder createDamageSourceBuilder(DamageType damageType) { return new CraftDamageSourceBuilder(damageType); } @Override public String get(Class aClass, String s) { if (aClass == Enchantment.class) { // We currently do not have any version-dependent remapping, so we can use current version return FieldRename.convertEnchantmentName(ApiVersion.CURRENT, s); } return s; } @Override public B get(Registry registry, NamespacedKey namespacedKey) { // We currently do not have any version-dependent remapping, so we can use current version return CraftRegistry.get(registry, namespacedKey, ApiVersion.CURRENT); } /** * 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; } }