Last active
August 14, 2019 23:29
-
-
Save theoparis/f7bb9e43e0737c935842a7ed87e8013d to your computer and use it in GitHub Desktop.
Spigot Minecraft Anvil GUI Helper Classes (Currently only works in Spigot/Minecraft 1.14.4)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.creepinson.plugin.util.gui; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.HashMap; | |
import org.bukkit.Bukkit; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.HandlerList; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.inventory.InventoryClickEvent; | |
import org.bukkit.event.inventory.InventoryCloseEvent; | |
import org.bukkit.inventory.Inventory; | |
import org.bukkit.inventory.ItemStack; | |
import org.bukkit.inventory.meta.ItemMeta; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import me.creepinson.plugin.util.NMSManager; | |
/** | |
* Created by chasechocolate. | |
*/ | |
public class AnvilGUI { | |
private Player player; | |
private AnvilClickEventHandler handler; | |
private static Class<?> BlockPosition; | |
private static Class<?> PacketPlayOutOpenWindow; | |
private static Class<?> ContainerAnvil; | |
private static Class<?> ContainerAccess; | |
private static Class<?> ChatMessage; | |
private static Class<?> EntityHuman; | |
private HashMap<AnvilSlot, ItemStack> items = new HashMap<AnvilSlot, ItemStack>(); | |
private Inventory inv; | |
private Listener listener; | |
private void loadClasses() { | |
BlockPosition = NMSManager.get().getNMSClass("BlockPosition"); | |
PacketPlayOutOpenWindow = NMSManager.get().getNMSClass("PacketPlayOutOpenWindow"); | |
ContainerAnvil = NMSManager.get().getNMSClass("ContainerAnvil"); | |
EntityHuman = NMSManager.get().getNMSClass("EntityHuman"); | |
ChatMessage = NMSManager.get().getNMSClass("ChatMessage"); | |
ContainerAccess = NMSManager.get().getNMSClass("ContainerAccess"); | |
} | |
public AnvilGUI(JavaPlugin plugin, final Player player, final AnvilClickEventHandler handler) { | |
loadClasses(); | |
this.player = player; | |
this.handler = handler; | |
this.listener = new Listener() { | |
@EventHandler | |
public void onInventoryClick(InventoryClickEvent event) { | |
if (event.getWhoClicked() instanceof Player) { | |
if (event.getInventory().equals(inv)) { | |
ItemStack item = event.getCurrentItem(); | |
int slot = event.getRawSlot(); | |
String name = ""; | |
if (item != null) { | |
if (item.hasItemMeta()) { | |
ItemMeta meta = item.getItemMeta(); | |
if (meta.hasDisplayName()) { | |
name = meta.getDisplayName(); | |
} | |
} | |
} | |
AnvilClickEvent clickEvent = new AnvilClickEvent(AnvilSlot.bySlot(slot), name, | |
getItemNameText()); | |
handler.onAnvilClick(clickEvent); | |
if(clickEvent.isCanceled) { | |
event.setCancelled(true); | |
} | |
if (clickEvent.getWillClose()) { | |
event.getWhoClicked().closeInventory(); | |
} | |
if (clickEvent.getWillDestroy()) { | |
destroy(); | |
} | |
} | |
} | |
} | |
@EventHandler | |
public void onInventoryClose(InventoryCloseEvent event) { | |
if (event.getPlayer() instanceof Player) { | |
Inventory inv = event.getInventory(); | |
if (inv.equals(AnvilGUI.this.inv)) { | |
inv.clear(); | |
destroy(); | |
} | |
} | |
} | |
}; | |
Bukkit.getPluginManager().registerEvents(listener, plugin); | |
} | |
public Player getPlayer() { | |
return player; | |
} | |
public void setSlot(AnvilSlot slot, ItemStack item) { | |
items.put(slot, item); | |
} | |
public void open() throws IllegalAccessException, InvocationTargetException, InstantiationException { | |
try { | |
Object p = NMSManager.get().getHandle(player); | |
Object container = null; | |
Method containerAccessAt = null; | |
switch (NMSManager.get().getVersion()) { | |
case "v1_14_R1": | |
containerAccessAt = NMSManager.get().getMethod(ContainerAccess, "at", | |
NMSManager.get().getNMSClass("World"), BlockPosition); | |
container = ContainerAnvil | |
.getConstructor(int.class, NMSManager.get().getNMSClass("PlayerInventory"), ContainerAccess) | |
.newInstance(1, NMSManager.get().getPlayerField(player, "inventory"), | |
containerAccessAt.invoke(null, NMSManager.get().getPlayerField(player, "world"), | |
BlockPosition.getConstructor(int.class, int.class, int.class).newInstance(0, 0, | |
0))); | |
default: | |
containerAccessAt = NMSManager.get().getMethod(ContainerAccess, "at", | |
NMSManager.get().getNMSClass("World"), BlockPosition); | |
container = ContainerAnvil | |
.getConstructor(int.class, NMSManager.get().getNMSClass("PlayerInventory"), ContainerAccess) | |
.newInstance(1, NMSManager.get().getPlayerField(player, "inventory"), | |
containerAccessAt.invoke(null, NMSManager.get().getPlayerField(player, "world"), | |
BlockPosition.getConstructor(int.class, int.class, int.class).newInstance(0, 0, | |
0))); | |
} | |
NMSManager.get().getField(NMSManager.get().getNMSClass("Container"), "checkReachable").set(container, | |
false); | |
// Set the items to the items from the inventory given | |
Object bukkitView = NMSManager.get().invokeMethod("getBukkitView", container); | |
inv = (Inventory) NMSManager.get().invokeMethod("getTopInventory", bukkitView); | |
for (AnvilSlot slot : items.keySet()) { | |
inv.setItem(slot.getSlot(), items.get(slot)); | |
} | |
// Counter stuff that the game uses to keep track of inventories | |
int c = (int) NMSManager.get().invokeMethod("nextContainerCounter", p); | |
// Send the packet | |
Object playerConnection = null; | |
Object packet = null; | |
switch (NMSManager.get().getVersion()) { | |
case "v1_14_R1": | |
Constructor<?> chatMessageConstructor = ChatMessage.getConstructor(String.class, Object[].class); | |
playerConnection = NMSManager.get().getPlayerField(player, "playerConnection"); | |
packet = PacketPlayOutOpenWindow.getConstructor(int.class, NMSManager.get().getNMSClass("Containers"), | |
NMSManager.get().getNMSClass("IChatBaseComponent")).newInstance(c, NMSManager.get().getNMSClass("Containers").getField("ANVIL").get(null), | |
chatMessageConstructor.newInstance("Repairing", new Object[] {})); | |
default: | |
chatMessageConstructor = ChatMessage.getConstructor(String.class, Object[].class); | |
playerConnection = NMSManager.get().getPlayerField(player, "playerConnection"); | |
packet = PacketPlayOutOpenWindow.getConstructor(int.class, NMSManager.get().getNMSClass("Containers"), | |
NMSManager.get().getNMSClass("IChatBaseComponent")).newInstance(c, NMSManager.get().getNMSClass("Containers").getField("ANVIL").get(null), | |
chatMessageConstructor.newInstance("Repairing", new Object[] {})); | |
} | |
Method sendPacket = NMSManager.get().getMethod("sendPacket", playerConnection.getClass(), | |
PacketPlayOutOpenWindow); | |
sendPacket.invoke(playerConnection, packet); | |
// Set their active container to the container | |
Field activeContainerField = NMSManager.get().getField(EntityHuman, "activeContainer"); | |
if (activeContainerField != null) { | |
activeContainerField.set(p, container); | |
// Set their active container window id to that counter stuff | |
NMSManager.get().getField(NMSManager.get().getNMSClass("Container"), "windowId") | |
.set(activeContainerField.get(p), c); | |
// Add the slot listener | |
NMSManager.get().getMethod("addSlotListener", activeContainerField.get(p).getClass(), p.getClass()) | |
.invoke(activeContainerField.get(p), p); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
public void destroy() { | |
player = null; | |
handler = null; | |
items = null; | |
HandlerList.unregisterAll(listener); | |
listener = null; | |
} | |
public String getItemNameText() { | |
// field: repairedItemName | |
Object p = NMSManager.get().getHandle(player); | |
Field activeContainerField = NMSManager.get().getField(EntityHuman, "activeContainer"); | |
if (activeContainerField != null) { | |
try { | |
Field renameText = NMSManager.get().getField(ContainerAnvil, "renameText"); | |
if (renameText != null) { | |
return (String) renameText.get(activeContainerField.get(p)); | |
} | |
} catch (IllegalArgumentException | IllegalAccessException e) { | |
e.printStackTrace(); | |
} | |
} | |
return null; | |
} | |
public enum AnvilSlot { | |
INPUT_LEFT(0), INPUT_RIGHT(1), OUTPUT(2); | |
private int slot; | |
private AnvilSlot(int slot) { | |
this.slot = slot; | |
} | |
public static AnvilSlot bySlot(int slot) { | |
for (AnvilSlot anvilSlot : values()) { | |
if (anvilSlot.getSlot() == slot) { | |
return anvilSlot; | |
} | |
} | |
return null; | |
} | |
public int getSlot() { | |
return slot; | |
} | |
} | |
public interface AnvilClickEventHandler { | |
void onAnvilClick(AnvilClickEvent event); | |
} | |
public class AnvilClickEvent { | |
private AnvilSlot slot; | |
private String name; | |
private String renameText; | |
private boolean close = false; | |
private boolean destroy = false; | |
public boolean isCanceled; | |
public AnvilClickEvent(AnvilSlot slot, String name, String renameText) { | |
this.slot = slot; | |
this.name = name; | |
this.renameText = renameText; | |
} | |
public String getRenameText() { | |
return renameText; | |
} | |
public AnvilSlot getSlot() { | |
return slot; | |
} | |
public String getName() { | |
return name; | |
} | |
public boolean getWillClose() { | |
return close; | |
} | |
public void setWillClose(boolean close) { | |
this.close = close; | |
} | |
public boolean getWillDestroy() { | |
return destroy; | |
} | |
public void setWillDestroy(boolean destroy) { | |
this.destroy = destroy; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.creepinson.command; | |
import java.lang.reflect.InvocationTargetException; | |
import org.bukkit.command.Command; | |
import org.bukkit.command.CommandExecutor; | |
import org.bukkit.command.CommandSender; | |
import org.bukkit.entity.Player; | |
import me.creepinson.core.HelpPlugin; | |
import me.creepinson.plugin.util.gui.AnvilGUI; | |
import me.creepinson.plugin.util.gui.AnvilGUI.AnvilClickEvent; | |
import me.creepinson.plugin.util.gui.AnvilGUI.AnvilClickEventHandler; | |
import me.creepinson.plugin.util.gui.AnvilGUI.AnvilSlot; | |
public class Anvil implements CommandExecutor { | |
@Override | |
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { | |
if (sender instanceof Player) { | |
Player p = (Player) sender; | |
AnvilGUI gui = new AnvilGUI(HelpPlugin.plugin, p, new AnvilClickEventHandler() { | |
@Override | |
public void onAnvilClick(AnvilClickEvent event) { | |
if (event.getSlot() != AnvilSlot.OUTPUT) { | |
p.sendMessage("Rename Text: " + event.getRenameText()); | |
} | |
} | |
}); | |
try { | |
gui.open(); | |
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { | |
e.printStackTrace(); | |
} | |
} | |
return true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package me.creepinson.plugin.util; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.HashMap; | |
import java.util.Map; | |
import org.bukkit.Bukkit; | |
import org.bukkit.entity.Player; | |
public class NMSManager { | |
private static NMSManager instance; | |
public static NMSManager get(){ | |
if(instance == null) | |
instance = new NMSManager(); | |
return instance; | |
} | |
public static final Map<Class<?>, Class<?>> CORRESPONDING_TYPES = new HashMap<Class<?>, Class<?>>(); | |
public Class<?> getPrimitiveType(Class<?> clazz) { | |
return CORRESPONDING_TYPES.containsKey(clazz) ? CORRESPONDING_TYPES | |
.get(clazz) : clazz; | |
} | |
public Class<?>[] toPrimitiveTypeArray(Class<?>[] classes) { | |
int a = classes != null ? classes.length : 0; | |
Class<?>[] types = new Class<?>[a]; | |
for (int i = 0; i < a; i++) | |
types[i] = getPrimitiveType(classes[i]); | |
return types; | |
} | |
public static boolean equalsTypeArray(Class<?>[] a, Class<?>[] o) { | |
if (a.length != o.length) | |
return false; | |
for (int i = 0; i < a.length; i++) | |
if (!a[i].equals(o[i]) && !a[i].isAssignableFrom(o[i])) | |
return false; | |
return true; | |
} | |
public Object getHandle(Object obj) { | |
try { | |
return getMethod("getHandle", obj.getClass()).invoke(obj); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return null; | |
} | |
} | |
public Object invokeMethod(String method, Object obj) { | |
try { | |
return getMethod(method, obj.getClass()).invoke(obj); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return null; | |
} | |
} | |
public Object invokeMethodWithArgs(String method, Object obj, Object... args) { | |
try { | |
return getMethod(method, obj.getClass()).invoke(obj, args); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return null; | |
} | |
} | |
public Method getMethod(String name, Class<?> clazz, | |
Class<?>... paramTypes) { | |
Class<?>[] t = toPrimitiveTypeArray(paramTypes); | |
for (Method m : clazz.getMethods()) { | |
Class<?>[] types = toPrimitiveTypeArray(m.getParameterTypes()); | |
if (m.getName().equals(name) && equalsTypeArray(types, t)) | |
return m; | |
} | |
return null; | |
} | |
public String getVersion() { | |
String name = Bukkit.getServer().getClass().getPackage().getName(); | |
return name.substring(name.lastIndexOf('.') + 1) + "."; | |
} | |
public Class<?> getNMSClass(String className) { | |
String fullName = "net.minecraft.server." + getVersion() + className; | |
Class<?> clazz = null; | |
try { | |
clazz = Class.forName(fullName); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return clazz; | |
} | |
public Field getField(Class<?> clazz, String name) { | |
try { | |
Field field = clazz.getDeclaredField(name); | |
field.setAccessible(true); | |
return field; | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return null; | |
} | |
} | |
public static boolean set(Object object, String fieldName, Object fieldValue) { | |
Class<?> clazz = object.getClass(); | |
while (clazz != null) { | |
try { | |
Field field = clazz.getDeclaredField(fieldName); | |
field.setAccessible(true); | |
field.set(object, fieldValue); | |
return true; | |
} catch (NoSuchFieldException e) { | |
clazz = clazz.getSuperclass(); | |
} catch (Exception e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
return false; | |
} | |
public Object getPlayerField(Player player, String name) throws SecurityException, NoSuchMethodException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { | |
Method getHandle = player.getClass().getMethod("getHandle"); | |
Object nmsPlayer = getHandle.invoke(player); | |
Field field = nmsPlayer.getClass().getField(name); | |
return field.get(nmsPlayer); | |
} | |
public Method getMethod(Class<?> clazz, String name, Class<?>... args) { | |
for (Method m : clazz.getMethods()) | |
if (m.getName().equals(name) | |
&& (args.length == 0 || ClassListEqual(args, | |
m.getParameterTypes()))) { | |
m.setAccessible(true); | |
return m; | |
} | |
return null; | |
} | |
public boolean ClassListEqual(Class<?>[] l1, Class<?>[] l2) { | |
boolean equal = true; | |
if (l1.length != l2.length) | |
return false; | |
for (int i = 0; i < l1.length; i++) | |
if (l1[i] != l2[i]) { | |
equal = false; | |
break; | |
} | |
return equal; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment