Add ItemStack 1.8.8+ cross-version PersistentDataContainer (#4143)

This PR itself does nothing on its own but creates the underlying backbone I need to make a less hacky solution in #3963 lmfao.

This PR creates a provider which uses NBT on 1.8.8-1.13 to mimic the exact structure of a PersistentDataContainer on 1.14+ which will allow us make any possible upgrades (which don't die from the lack of DFU on >1.13) work as expected. Additionally, this does not use reflection on modern Minecraft versions and thus will not need to be maintained/updated on MC version updates.

In the future, we will need to find a way to store data on tile entities (signs namely) so that we are able to store UUIDs on signs for future plans, but for now ItemStacks work to fix our spawner issues.
This commit is contained in:
Josh Roy 2021-05-28 07:23:44 -04:00 committed by GitHub
parent 4ed36d9bbe
commit ec50b28f4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 212 additions and 1 deletions

View File

@ -47,6 +47,7 @@ import net.ess3.api.IJails;
import net.ess3.api.ISettings;
import net.ess3.nms.refl.providers.ReflFormattedCommandAliasProvider;
import net.ess3.nms.refl.providers.ReflKnownCommandsProvider;
import net.ess3.nms.refl.providers.ReflPersistentDataProvider;
import net.ess3.nms.refl.providers.ReflServerStateProvider;
import net.ess3.nms.refl.providers.ReflSpawnEggProvider;
import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider;
@ -55,6 +56,7 @@ import net.ess3.provider.ContainerProvider;
import net.ess3.provider.FormattedCommandAliasProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.PotionMetaProvider;
import net.ess3.provider.ProviderListener;
import net.ess3.provider.ServerStateProvider;
@ -69,6 +71,7 @@ import net.ess3.provider.providers.BukkitSpawnerBlockProvider;
import net.ess3.provider.providers.FlatSpawnEggProvider;
import net.ess3.provider.providers.LegacyPotionMetaProvider;
import net.ess3.provider.providers.LegacySpawnEggProvider;
import net.ess3.provider.providers.ModernPersistentDataProvider;
import net.ess3.provider.providers.PaperContainerProvider;
import net.ess3.provider.providers.PaperKnownCommandsProvider;
import net.ess3.provider.providers.PaperMaterialTagProvider;
@ -154,6 +157,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient ProviderListener recipeBookEventProvider;
private transient MaterialTagProvider materialTagProvider;
private transient SyncCommandsProvider syncCommandsProvider;
private transient PersistentDataProvider persistentDataProvider;
private transient Kits kits;
private transient RandomTeleport randomTeleport;
private transient UpdateChecker updateChecker;
@ -383,6 +387,12 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
// Sync Commands Provider
syncCommandsProvider = new ReflSyncCommandsProvider();
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_14_4_R01)) {
persistentDataProvider = new ModernPersistentDataProvider(this);
} else {
persistentDataProvider = new ReflPersistentDataProvider(this);
}
execTimer.mark("Init(Providers)");
reload();
@ -1143,6 +1153,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return syncCommandsProvider;
}
@Override
public PersistentDataProvider getPersistentDataProvider() {
return persistentDataProvider;
}
@Override
public PluginCommand getPluginCommand(final String cmd) {
return this.getCommand(cmd);

View File

@ -6,10 +6,11 @@ import com.earth2me.essentials.api.IWarps;
import com.earth2me.essentials.commands.IEssentialsCommand;
import com.earth2me.essentials.perm.PermissionsHandler;
import com.earth2me.essentials.updatecheck.UpdateChecker;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.ContainerProvider;
import net.ess3.provider.FormattedCommandAliasProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.MaterialTagProvider;
import net.ess3.provider.PersistentDataProvider;
import net.ess3.provider.ServerStateProvider;
import net.ess3.provider.SpawnerBlockProvider;
import net.ess3.provider.SpawnerItemProvider;
@ -139,5 +140,7 @@ public interface IEssentials extends Plugin {
SyncCommandsProvider getSyncCommandsProvider();
PersistentDataProvider getPersistentDataProvider();
PluginCommand getPluginCommand(String cmd);
}

View File

@ -0,0 +1,11 @@
package net.ess3.provider;
import org.bukkit.inventory.ItemStack;
public interface PersistentDataProvider extends Provider {
void set(ItemStack itemStack, String key, String value);
String getString(ItemStack itemStack, String key);
void remove(ItemStack itemStack, String key);
}

View File

@ -0,0 +1,51 @@
package net.ess3.provider.providers;
import net.ess3.provider.PersistentDataProvider;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
@SuppressWarnings("ConstantConditions")
public class ModernPersistentDataProvider implements PersistentDataProvider {
private final Plugin plugin;
public ModernPersistentDataProvider(Plugin plugin) {
this.plugin = plugin;
}
@Override
public void set(ItemStack itemStack, String key, String value) {
if (itemStack == null || itemStack.getItemMeta() == null || key == null || value == null) {
return;
}
final ItemMeta im = itemStack.getItemMeta();
im.getPersistentDataContainer().set(new NamespacedKey(plugin, key), PersistentDataType.STRING, value);
itemStack.setItemMeta(im);
}
@Override
public String getString(ItemStack itemStack, String key) {
if (itemStack == null || itemStack.getItemMeta() == null || key == null) {
return null;
}
try {
return itemStack.getItemMeta().getPersistentDataContainer().get(new NamespacedKey(plugin, key), PersistentDataType.STRING);
} catch (IllegalArgumentException ignored) {
return null;
}
}
@Override
public void remove(ItemStack itemStack, String key) {
itemStack.getItemMeta().getPersistentDataContainer().remove(new NamespacedKey(plugin, key));
}
@Override
public String getDescription() {
return "1.14+ Persistent Data Container Provider";
}
}

View File

@ -0,0 +1,131 @@
package net.ess3.nms.refl.providers;
import net.ess3.nms.refl.ReflUtil;
import net.ess3.provider.PersistentDataProvider;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.Locale;
/**
* Stores persistent data on 1.18-1.13 in a manner that's consistent with PDC on 1.14+ to enable
* seamless upgrades.
*/
public class ReflPersistentDataProvider implements PersistentDataProvider {
private static final String PDC_ROOT_TAG = "PublicBukkitValues";
private static final String ROOT_TAG = "tag";
private final String namespace;
private final MethodHandle itemHandleGetterHandle;
private final MethodHandle getTagHandle;
private final MethodHandle tagSetterHandle;
private final MethodHandle newCompoundHandle;
private final MethodHandle getCompoundHandle;
private final MethodHandle setCompoundHandle;
private final MethodHandle setStringHandle;
private final MethodHandle removeHandle;
private final MethodHandle getStringHandle;
public ReflPersistentDataProvider(Plugin plugin) {
this.namespace = plugin.getName().toLowerCase(Locale.ROOT);
MethodHandle itemHandleGetterHandle = null;
MethodHandle getTagHandle = null;
MethodHandle tagSetterHandle = null;
MethodHandle newCompoundHandle = null;
MethodHandle getCompoundHandle = null;
MethodHandle setCompoundHandle = null;
MethodHandle setStringHandle = null;
MethodHandle removeHandle = null;
MethodHandle getStringHandle = null;
try {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
final Field handleGetter = ReflUtil.getOBCClass("inventory.CraftItemStack").getDeclaredField("handle");
handleGetter.setAccessible(true);
itemHandleGetterHandle = lookup.unreflectGetter(handleGetter);
final Field tagSetter = ReflUtil.getNMSClass("ItemStack").getDeclaredField("tag");
tagSetter.setAccessible(true);
tagSetterHandle = lookup.unreflectSetter(tagSetter);
getTagHandle = lookup.findVirtual(ReflUtil.getNMSClass("ItemStack"), "getTag", MethodType.methodType(ReflUtil.getNMSClass("NBTTagCompound")));
newCompoundHandle = lookup.findConstructor(ReflUtil.getNMSClass("NBTTagCompound"), MethodType.methodType(void.class));
getCompoundHandle = lookup.findVirtual(ReflUtil.getNMSClass("NBTTagCompound"), "getCompound", MethodType.methodType(ReflUtil.getNMSClass("NBTTagCompound"), String.class));
setCompoundHandle = lookup.findVirtual(ReflUtil.getNMSClass("NBTTagCompound"), "set", MethodType.methodType(void.class, String.class, ReflUtil.getNMSClass("NBTBase")));
setStringHandle = lookup.findVirtual(ReflUtil.getNMSClass("NBTTagCompound"), "setString", MethodType.methodType(void.class, String.class, String.class));
removeHandle = lookup.findVirtual(ReflUtil.getNMSClass("NBTTagCompound"), "remove", MethodType.methodType(void.class, String.class));
getStringHandle = lookup.findVirtual(ReflUtil.getNMSClass("NBTTagCompound"), "getString", MethodType.methodType(String.class, String.class));
} catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
this.itemHandleGetterHandle = itemHandleGetterHandle;
this.getTagHandle = getTagHandle;
this.tagSetterHandle = tagSetterHandle;
this.newCompoundHandle = newCompoundHandle;
this.getCompoundHandle = getCompoundHandle;
this.setCompoundHandle = setCompoundHandle;
this.setStringHandle = setStringHandle;
this.removeHandle = removeHandle;
this.getStringHandle = getStringHandle;
}
private String getPersistentString(ItemStack itemStack, String key) throws Throwable {
final Object nmsItem = itemHandleGetterHandle.invoke(itemStack);
final Object itemRootTag = getTagHandle.invoke(nmsItem);
if (itemRootTag == null) {
return null;
}
final Object tagCompound = getCompoundHandle.invoke(itemRootTag, ROOT_TAG);
final Object publicBukkitValuesCompound = getCompoundHandle.invoke(tagCompound, PDC_ROOT_TAG);
return (String) getStringHandle.invoke(publicBukkitValuesCompound, key);
}
private void setPersistentString(ItemStack itemStack, String key, String value) throws Throwable {
final Object nmsItem = itemHandleGetterHandle.invoke(itemStack);
Object itemRootTag = getTagHandle.invoke(nmsItem);
if (itemRootTag == null) {
itemRootTag = newCompoundHandle.invoke();
tagSetterHandle.invoke(nmsItem, itemRootTag);
}
final Object tagCompound = getCompoundHandle.invoke(itemRootTag, ROOT_TAG);
final Object publicBukkitValuesCompound = getCompoundHandle.invoke(tagCompound, PDC_ROOT_TAG);
if (value == null) {
removeHandle.invoke(publicBukkitValuesCompound, key);
} else {
setStringHandle.invoke(publicBukkitValuesCompound, key, value);
}
setCompoundHandle.invoke(tagCompound, PDC_ROOT_TAG, publicBukkitValuesCompound);
setCompoundHandle.invoke(itemRootTag, ROOT_TAG, tagCompound);
}
@Override
public void set(ItemStack itemStack, String key, String value) {
try {
setPersistentString(itemStack, namespace + ":" + key, value);
} catch (Throwable ignored) {
}
}
@Override
public String getString(ItemStack itemStack, String key) {
try {
return getPersistentString(itemStack, namespace + ":" + key);
} catch (Throwable ignored) {
return null;
}
}
@Override
public void remove(ItemStack itemStack, String key) {
try {
setPersistentString(itemStack, namespace + ":" + key, null);
} catch (Throwable ignored) {
}
}
@Override
public String getDescription() {
return "1.13 >= Persistent Data Container Provider";
}
}