CraftBukkit/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java

444 lines
17 KiB
Java

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> BLOCK_MATERIAL = new HashMap<>();
private static final Map<Item, Material> ITEM_MATERIAL = new HashMap<>();
private static final Map<Material, Item> MATERIAL_ITEM = new HashMap<>();
private static final Map<Material, Block> 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<NBTBase> name = new Dynamic<>(DynamicOpsNBT.INSTANCE, NBTTagString.valueOf("minecraft:" + material.toLowerCase(Locale.ROOT)));
Dynamic<NBTBase> 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<Attribute, AttributeModifier> getDefaultAttributeModifiers(Material material, EquipmentSlot slot) {
ImmutableMultimap.Builder<Attribute, AttributeModifier> 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 extends Keyed> B get(Registry<B> 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.
* <p>
* 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;
}
}