From 792bfdd358e9ac2bb5348ce827e2f027f2bcf431 Mon Sep 17 00:00:00 2001 From: rolyPolyVole Date: Sun, 11 Aug 2024 22:34:16 +0700 Subject: [PATCH] Added ReflectUtil class, successfully replaced the crossbow registry entry with a custom class. --- build.gradle.kts | 20 +- .../FireworkWarsPlugin.java | 24 ++- .../items/FireworkRifleItem.java | 16 +- .../firework_wars_plugin/items/RifleAmmo.java | 2 +- .../items/manager/AbstractItem.java | 2 +- .../items/manager/CustomItemManager.java | 93 +++++++-- .../util/ReflectUtil.java | 176 ++++++++++++++++++ 7 files changed, 295 insertions(+), 38 deletions(-) create mode 100644 src/main/java/net/slqmy/firework_wars_plugin/util/ReflectUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index 3ff307cc..ba0e8bb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,21 +15,21 @@ plugins { } val groupStringSeparator = "." -val kebabcaseStringSeparator = "-" -val snakecaseStringSeparator = "_" +val kebabCaseStringSeparator = "-" +val snakeCaseStringSeparator = "_" fun capitalizeFirstLetter(string: String): String { return string.first().uppercase() + string.slice(IntRange(1, string.length - 1)) } -fun snakecase(kebabcaseString: String): String { - return kebabcaseString.lowercase().replace(kebabcaseStringSeparator, snakecaseStringSeparator) +fun snakeCase(kebabCaseString: String): String { + return kebabCaseString.lowercase().replace(kebabCaseStringSeparator, snakeCaseStringSeparator) } -fun pascalcase(kebabcaseString: String): String { +fun pascalCase(kebabCaseString: String): String { var pascalCaseString = "" - val splitString = kebabcaseString.split(kebabcaseStringSeparator) + val splitString = kebabCaseString.split(kebabCaseStringSeparator) for (part in splitString) { pascalCaseString += capitalizeFirstLetter(part) @@ -41,13 +41,13 @@ fun pascalcase(kebabcaseString: String): String { description = "A Minecraft Paper plugin that adds a firework-focused PvP gamemode." val mainProjectAuthor = "Slqmy" -val projectAuthors = listOfNotNull(mainProjectAuthor, "rolyPolyVole") +val mainestProjectAuthor = "rolyPolyVole" +val projectAuthors = listOfNotNull(mainProjectAuthor, mainestProjectAuthor) val topLevelDomain = "net" - val projectNameString = rootProject.name -group = topLevelDomain + groupStringSeparator + mainProjectAuthor.lowercase() + groupStringSeparator + snakecase(projectNameString) +group = topLevelDomain + groupStringSeparator + mainProjectAuthor.lowercase() + groupStringSeparator + snakeCase(projectNameString) version = "1.0.0" val buildDirectoryString = buildDir.toString() @@ -97,7 +97,7 @@ tasks { bukkitPluginYaml { authors = projectAuthors - main = projectGroupString + groupStringSeparator + pascalcase(projectNameString) + main = projectGroupString + groupStringSeparator + pascalCase(projectNameString) apiVersion = paperApiVersion load = BukkitPluginYaml.PluginLoadOrder.STARTUP diff --git a/src/main/java/net/slqmy/firework_wars_plugin/FireworkWarsPlugin.java b/src/main/java/net/slqmy/firework_wars_plugin/FireworkWarsPlugin.java index f3e761b3..016c5527 100644 --- a/src/main/java/net/slqmy/firework_wars_plugin/FireworkWarsPlugin.java +++ b/src/main/java/net/slqmy/firework_wars_plugin/FireworkWarsPlugin.java @@ -1,6 +1,10 @@ package net.slqmy.firework_wars_plugin; import net.slqmy.firework_wars_plugin.util.PersistentDataManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.framework.qual.DefaultQualifier; @@ -15,8 +19,10 @@ import net.slqmy.firework_wars_plugin.items.manager.CustomItemManager; import net.slqmy.firework_wars_plugin.language.LanguageManager; -@DefaultQualifier(NonNull.class) -public final class FireworkWarsPlugin extends JavaPlugin { +import java.util.logging.Logger; + +public final class FireworkWarsPlugin extends JavaPlugin implements Listener { + public static Logger LOGGER; private PlayerDataManager playerDataManager; private LanguageManager languageManager; @@ -49,6 +55,10 @@ public PersistentDataManager getPdcManager() { return this.pdcManager; } + public FireworkWarsPlugin() { + LOGGER = getLogger(); + } + @Override public void onEnable() { getDataFolder().mkdir(); @@ -68,6 +78,8 @@ public void onEnable() { new SetLanguageCommand(this); new ArenaCommand(this); + + getServer().getPluginManager().registerEvents(this, this); } @Override @@ -76,4 +88,12 @@ public void onDisable() { playerDataManager.save(); } } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + ItemStack item1 = customItemManager.getItem("firework_rifle_ammo").getItem(event.getPlayer()); + ItemStack item2 = customItemManager.getItem("firework_rifle").getItem(event.getPlayer()); + + event.getPlayer().getInventory().addItem(item1, item2); + } } diff --git a/src/main/java/net/slqmy/firework_wars_plugin/items/FireworkRifleItem.java b/src/main/java/net/slqmy/firework_wars_plugin/items/FireworkRifleItem.java index 47815127..2e460225 100644 --- a/src/main/java/net/slqmy/firework_wars_plugin/items/FireworkRifleItem.java +++ b/src/main/java/net/slqmy/firework_wars_plugin/items/FireworkRifleItem.java @@ -29,13 +29,13 @@ public FireworkRifleItem(FireworkWarsPlugin plugin) { } @Override - protected ItemStack getItem(Player player) { + public ItemStack getItem(Player player) { return new ItemBuilder(plugin, itemMaterial) .setName(plugin.getLanguageManager().getMessage(Message.FIREWORK_RIFLE, player)) .setLore(plugin.getLanguageManager().getMessage(Message.FIREWORK_RIFLE_LORE, player)) .setEnchanted(true) .setUnbreakable(true) - .itemSupplier(() -> plugin.getCustomItemManager().getNMSItem("firework_rifle_crossbow").getDefaultInstance().asBukkitCopy()) + .itemSupplier(() -> plugin.getCustomItemManager().getBukkitItemStackFromNMS("crossbow")) .modifyMeta(meta -> meta.addEnchant(Enchantment.QUICK_CHARGE, 3, true)) .build(); } @@ -46,17 +46,21 @@ public void onCrossbowLoad(EntityLoadCrossbowEvent event) { return; } - CrossbowMeta crossbowMeta = (CrossbowMeta) event.getCrossbow().getItemMeta(); + FireworkWarsGame game = plugin.getGameManager().getFireworkWarsGame(player); - ItemStack firework = new ItemStack(Material.FIREWORK_ROCKET); - FireworkMeta fireworkMeta = (FireworkMeta) firework.getItemMeta(); + if (game == null) { + return; + } - FireworkWarsGame game = plugin.getGameManager().getFireworkWarsGame(player); FireworkWarsTeam team = game.getTeam(player); + ItemStack firework = new ItemStack(Material.FIREWORK_ROCKET); + FireworkMeta fireworkMeta = (FireworkMeta) firework.getItemMeta(); + addFireworkStars(fireworkMeta, team.getConfiguredTeam().getColor()); firework.setItemMeta(fireworkMeta); + CrossbowMeta crossbowMeta = (CrossbowMeta) event.getCrossbow().getItemMeta(); crossbowMeta.setChargedProjectiles(List.of(firework)); } diff --git a/src/main/java/net/slqmy/firework_wars_plugin/items/RifleAmmo.java b/src/main/java/net/slqmy/firework_wars_plugin/items/RifleAmmo.java index 451bd201..73f1656c 100644 --- a/src/main/java/net/slqmy/firework_wars_plugin/items/RifleAmmo.java +++ b/src/main/java/net/slqmy/firework_wars_plugin/items/RifleAmmo.java @@ -17,7 +17,7 @@ public RifleAmmo(FireworkWarsPlugin plugin) { } @Override - protected ItemStack getItem(Player player) { + public ItemStack getItem(Player player) { return new ItemBuilder(plugin, itemMaterial) .setName(plugin.getLanguageManager().getMessage(Message.FIREWORK_RIFLE_AMMO, player)) .setLore(plugin.getLanguageManager().getMessage(Message.FIREWORK_RIFLE_AMMO_LORE, player)) diff --git a/src/main/java/net/slqmy/firework_wars_plugin/items/manager/AbstractItem.java b/src/main/java/net/slqmy/firework_wars_plugin/items/manager/AbstractItem.java index 0458ee47..c307f8d7 100644 --- a/src/main/java/net/slqmy/firework_wars_plugin/items/manager/AbstractItem.java +++ b/src/main/java/net/slqmy/firework_wars_plugin/items/manager/AbstractItem.java @@ -55,5 +55,5 @@ protected ItemStack getBaseItemStack() { return new ItemStack(itemMaterial); } - protected abstract ItemStack getItem(Player player); + public abstract ItemStack getItem(Player player); } diff --git a/src/main/java/net/slqmy/firework_wars_plugin/items/manager/CustomItemManager.java b/src/main/java/net/slqmy/firework_wars_plugin/items/manager/CustomItemManager.java index 722083f5..6fec8fdb 100644 --- a/src/main/java/net/slqmy/firework_wars_plugin/items/manager/CustomItemManager.java +++ b/src/main/java/net/slqmy/firework_wars_plugin/items/manager/CustomItemManager.java @@ -1,18 +1,30 @@ package net.slqmy.firework_wars_plugin.items.manager; +import com.mojang.serialization.Lifecycle; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceList; import net.minecraft.core.DefaultedMappedRegistry; +import net.minecraft.core.Holder; import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import net.slqmy.firework_wars_plugin.FireworkWarsPlugin; import net.slqmy.firework_wars_plugin.items.FireworkRifleItem; import net.slqmy.firework_wars_plugin.items.RifleAmmo; import net.slqmy.firework_wars_plugin.items.nms.CustomCrossbow; +import net.slqmy.firework_wars_plugin.util.ReflectUtil; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.sql.Ref; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; @@ -29,11 +41,12 @@ public CustomItemManager(FireworkWarsPlugin plugin) { registerItem(new FireworkRifleItem(plugin)); registerItem(new RifleAmmo(plugin)); - reopenItemRegistry(); + reopenItemRegistry(); //Prevent error registerNMSItem( - "firework_rifle_crossbow", - new CustomCrossbow(CustomCrossbow.PROPERTIES, getItem("firework_rifle_ammo"))); + "crossbow", + new CustomCrossbow(CustomCrossbow.PROPERTIES, getItem("firework_rifle_ammo")), + Items.CROSSBOW); BuiltInRegistries.ITEM.freeze(); } @@ -51,30 +64,74 @@ public AbstractItem getItem(String itemId) { } public Item getNMSItem(String itemId) { - return nmsItemRegistry.get(itemId); + return nmsItemRegistry.get(itemId); + } + + public ItemStack getBukkitItemStackFromNMS(String itemId) { + net.minecraft.world.item.ItemStack nmsItemStack = nmsItemRegistry.get(itemId).getDefaultInstance(); + ItemStack itemStack = nmsItemStack.asBukkitCopy(); + + itemStack.setItemMeta(CraftItemStack.getItemMeta(nmsItemStack)); + return itemStack; } private void registerItem(AbstractItem item) { itemRegistry.put(item.getItemId(), item); } - private void registerNMSItem(String id, Item item) { - Items.registerItem(id, item); + private void registerNMSItem(String id, Item item, Item override) { + overrideItemRegistryEntry(id, item, override); + nmsItemRegistry.put(id, item); } private void reopenItemRegistry() { - try { - Field frozenField = MappedRegistry.class.getDeclaredField("frozen"); - Field unregisteredHoldersField = MappedRegistry.class.getDeclaredField("unregisteredIntrusiveHolders"); - - frozenField.setAccessible(true); - unregisteredHoldersField.setAccessible(true); - - frozenField.setBoolean(BuiltInRegistries.ITEM, false); - unregisteredHoldersField.set(BuiltInRegistries.ITEM, new IdentityHashMap<>()); - } catch (IllegalAccessException | NoSuchFieldException e) { - plugin.getLogger().severe("Failed to reopen the item registry! " + e.getMessage()); - } + ReflectUtil.reflect(MappedRegistry.class, BuiltInRegistries.ITEM, () -> { + ReflectUtil.setFieldValue("frozen", false); + ReflectUtil.setFieldValue("unregisteredIntrusiveHolders", new IdentityHashMap<>()); + }); + } + + private void overrideItemRegistryEntry(String id, Item item, Item override) { + ResourceKey key = ResourceKey.create( + BuiltInRegistries.ITEM.key(), ResourceLocation.withDefaultNamespace(id)); + + RegistrationInfo info = RegistrationInfo.BUILT_IN; + + Holder.Reference holder = ReflectUtil.reflect(MappedRegistry.class, BuiltInRegistries.ITEM, () -> { + Map> map = ReflectUtil.getFieldValue("unregisteredIntrusiveHolders"); + return map.get(item); + }); + + ReflectUtil.reflect(Holder.Reference.class, holder, () -> { + ReflectUtil.invokeMethod("bindKey", new Class[] {ResourceKey.class}, key); + }); + + ReflectUtil.reflect(MappedRegistry.class, BuiltInRegistries.ITEM, () -> { + HashMap, Holder.Reference> byKey = ReflectUtil.getFieldValue("byKey"); + byKey.put(key, holder); + + HashMap> byLocation = ReflectUtil.getFieldValue("byLocation"); + byLocation.put(key.location(), holder); + + IdentityHashMap> byValue = ReflectUtil.getFieldValue("byValue"); + byValue.put(item, holder); + + ObjectArrayList> byId = ReflectUtil.getFieldValue("byId"); + byId.set(byId.indexOf(byValue.remove(override)), holder); + + Reference2IntOpenHashMap toId = ReflectUtil.getFieldValue("toId"); + toId.put(item, toId.getInt(override)); + toId.removeInt(override); + + IdentityHashMap, RegistrationInfo> registrationInfos = ReflectUtil.getFieldValue("registrationInfos"); + registrationInfos.put(key, info); + + Lifecycle lifecycle = ReflectUtil.getFieldValue("registryLifecycle"); + ReflectUtil.setFieldValue("registryLifecycle", lifecycle.add(info.lifecycle())); + + Map> unregisteredHolders = ReflectUtil.getFieldValue("unregisteredIntrusiveHolders"); + unregisteredHolders.remove(item); + }); } } diff --git a/src/main/java/net/slqmy/firework_wars_plugin/util/ReflectUtil.java b/src/main/java/net/slqmy/firework_wars_plugin/util/ReflectUtil.java new file mode 100644 index 00000000..565367f7 --- /dev/null +++ b/src/main/java/net/slqmy/firework_wars_plugin/util/ReflectUtil.java @@ -0,0 +1,176 @@ +package net.slqmy.firework_wars_plugin.util; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.slqmy.firework_wars_plugin.FireworkWarsPlugin; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.function.Supplier; + +@MethodsReturnNonnullByDefault +@SuppressWarnings("unused") +public final class ReflectUtil { + private static boolean reflecting; + + private static Class clazz; + private static Object instance; + + public static Field getField(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + FireworkWarsPlugin.LOGGER.warning("Failed to get field:" + e.getMessage()); + } + + throw new RuntimeException("Failed to get field!"); + } + + public static Field getField(String fieldName) { + if (!reflecting) { + throw new IllegalStateException("Cannot get field without class context outside a reflection function!"); + } + + return getField(clazz, fieldName); + } + + @SuppressWarnings("unchecked") + public static T getFieldValue(Field field, Object instance) { + try { + return (T) field.get(instance); + } catch (IllegalAccessException e) { + FireworkWarsPlugin.LOGGER.warning("Failed to get field value:" + e.getMessage()); + } + + throw new RuntimeException("Failed to get field value!"); + } + + public static T getFieldValue(Field field) { + if (!reflecting) { + throw new IllegalStateException("Cannot get field value without class context outside a reflection function!"); + } + + return getFieldValue(field, instance); + } + + public static T getFieldValue(Class clazz, String fieldName, Object instance) { + Field field = getField(clazz, fieldName); + return getFieldValue(field, instance); + } + + public static T getFieldValue(String fieldName) { + if (!reflecting) { + throw new IllegalStateException("Cannot get field value without class context outside a reflection function!"); + } + + return getFieldValue(clazz, fieldName, instance); + } + + public static void setFieldValue(Field field, Object instance, Object value) { + try { + field.set(instance, value); + } catch (IllegalAccessException e) { + FireworkWarsPlugin.LOGGER.warning("Failed to set field value:" + e.getMessage()); + } + } + + public static void setFieldValue(Field field, Object value) { + if (!reflecting) { + throw new IllegalStateException("Cannot set field value without class context outside a reflection function!"); + } + + setFieldValue(field, instance, value); + } + + public static void setFieldValue(Class clazz, String fieldName, Object instance, Object value) { + Field field = getField(clazz, fieldName); + setFieldValue(field, instance, value); + } + + public static void setFieldValue(String fieldName, Object value) { + if (!reflecting) { + throw new IllegalStateException("Cannot set field value without class context outside a reflection function!"); + } + + setFieldValue(clazz, fieldName, instance, value); + } + + public static Method getMethod(Class clazz, String methodName, Class... parameterTypes) { + try { + Method method = clazz.getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + FireworkWarsPlugin.LOGGER.warning("Failed to get method:" + e.getMessage()); + } + + throw new RuntimeException("Failed to get method!"); + } + + public static Method getMethod(String methodName, Class... parameterTypes) { + if (!reflecting) { + throw new IllegalStateException("Cannot get method without class context outside a reflection function!"); + } + + return getMethod(clazz, methodName, parameterTypes); + } + + @SuppressWarnings("unchecked") + public static T invokeMethod(Method method, Object instance, Object... args) { + try { + return (T) method.invoke(instance, args); + } catch (Exception e) { + FireworkWarsPlugin.LOGGER.warning("Failed to invoke method:" + e.getMessage()); + } + + throw new RuntimeException("Failed to invoke method!"); + } + + public static T invokeMethod(Method method, Object... args) { + if (!reflecting) { + throw new IllegalStateException("Cannot invoke method without class context outside a reflection function!"); + } + + return invokeMethod(method, instance, args); + } + + public static T invokeMethod(Class clazz, String methodName, Class[] parameterTypes, Object instance, Object... args) { + Method method = getMethod(clazz, methodName, parameterTypes); + return invokeMethod(method, instance, args); + } + + public static T invokeMethod(String methodName, Class[] parameterTypes, Object... args) { + if (!reflecting) { + throw new IllegalStateException("Cannot invoke method without class context outside a reflection function!"); + } + + return invokeMethod(clazz, methodName, parameterTypes, instance, args); + } + + public static void reflect(Class clazz, Object instance, Runnable runnable) { + ReflectUtil.reflecting = true; + ReflectUtil.clazz = clazz; + ReflectUtil.instance = instance; + + runnable.run(); + + ReflectUtil.reflecting = false; + ReflectUtil.clazz = null; + ReflectUtil.instance = null; + } + + public static T reflect(Class clazz, Object instance, Supplier supplier) { + ReflectUtil.reflecting = true; + ReflectUtil.clazz = clazz; + ReflectUtil.instance = instance; + + T value = supplier.get(); + + ReflectUtil.reflecting = false; + ReflectUtil.clazz = null; + ReflectUtil.instance = null; + + return value; + } +}