package com.plotsquared.bukkit.titles; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; /** * Minecraft 1.8 Title * For 1.11 * * @author Maxim Van de Wynckel * @version 1.1.0 */ public class TitleManager_1_11 { /* Title packet */ private static Class<?> packetTitle; /* Title packet actions ENUM */ private static Class<?> packetActions; /* Chat serializer */ private static Class<?> nmsChatSerializer; private static Class<?> chatBaseComponent; /* NMS player and connection */ private static Class<?> nmsPlayer; private static Class<?> nmsPlayerConnection; private static Field playerConnection; private static Method sendPacket; private static Class<?> obcPlayer; private static Method methodPlayerGetHandle; /* Title text and color */ private String title = ""; private ChatColor titleColor = ChatColor.WHITE; /* Subtitle text and color */ private String subtitle = ""; private ChatColor subtitleColor = ChatColor.WHITE; /* Title timings */ private int fadeInTime = -1; private int stayTime = -1; private int fadeOutTime = -1; private boolean ticks = false; private static final Map<Class<?>, Class<?>> CORRESPONDING_TYPES = new HashMap<Class<?>, Class<?>>(); public TitleManager_1_11() { loadClasses(); } /** * Create a new 1.8 title * * @param title Title */ public TitleManager_1_11(String title) { this.title = title; loadClasses(); } /** * Create a new 1.8 title * * @param title Title text * @param subtitle Subtitle text */ public TitleManager_1_11(String title, String subtitle) { this.title = title; this.subtitle = subtitle; loadClasses(); } /** * Copy 1.8 title * * @param title Title */ public TitleManager_1_11(TitleManager_1_11 title) { // Copy title this.title = title.getTitle(); this.subtitle = title.getSubtitle(); this.titleColor = title.getTitleColor(); this.subtitleColor = title.getSubtitleColor(); this.fadeInTime = title.getFadeInTime(); this.fadeOutTime = title.getFadeOutTime(); this.stayTime = title.getStayTime(); this.ticks = title.isTicks(); loadClasses(); } /** * Create a new 1.8 title * * @param title Title text * @param subtitle Subtitle text * @param fadeInTime Fade in time * @param stayTime Stay on screen time * @param fadeOutTime Fade out time */ public TitleManager_1_11(String title, String subtitle, int fadeInTime, int stayTime, int fadeOutTime) { this.title = title; this.subtitle = subtitle; this.fadeInTime = fadeInTime; this.stayTime = stayTime; this.fadeOutTime = fadeOutTime; loadClasses(); } /** * Load spigot and NMS classes */ private void loadClasses() { if (packetTitle == null) { packetTitle = getNMSClass("PacketPlayOutTitle"); packetActions = getNMSClass("PacketPlayOutTitle$EnumTitleAction"); chatBaseComponent = getNMSClass("IChatBaseComponent"); nmsChatSerializer = getNMSClass("ChatComponentText"); nmsPlayer = getNMSClass("EntityPlayer"); nmsPlayerConnection = getNMSClass("PlayerConnection"); playerConnection = getField(nmsPlayer, "playerConnection"); sendPacket = getMethod(nmsPlayerConnection, "sendPacket"); obcPlayer = getOBCClass("entity.CraftPlayer"); methodPlayerGetHandle = getMethod("getHandle", obcPlayer); } } /** * Set title text * * @param title Title */ public void setTitle(String title) { this.title = title; } /** * Get title text * * @return Title text */ public String getTitle() { return this.title; } /** * Set subtitle text * * @param subtitle Subtitle text */ public void setSubtitle(String subtitle) { this.subtitle = subtitle; } /** * Get subtitle text * * @return Subtitle text */ public String getSubtitle() { return this.subtitle; } /** * Set the title color * * @param color Chat color */ public void setTitleColor(ChatColor color) { this.titleColor = color; } /** * Set the subtitle color * * @param color Chat color */ public void setSubtitleColor(ChatColor color) { this.subtitleColor = color; } /** * Set title fade in time * * @param time Time */ public void setFadeInTime(int time) { this.fadeInTime = time; } /** * Set title fade out time * * @param time Time */ public void setFadeOutTime(int time) { this.fadeOutTime = time; } /** * Set title stay time * * @param time Time */ public void setStayTime(int time) { this.stayTime = time; } /** * Set timings to ticks */ public void setTimingsToTicks() { ticks = true; } /** * Set timings to seconds */ public void setTimingsToSeconds() { ticks = false; } /** * Send the title to a player * * @param player Player */ public void send(Player player) { if (packetTitle != null) { // First reset previous settings resetTitle(player); try { // Send timings first Object handle = getHandle(player); Object connection = playerConnection.get(handle); Object[] actions = packetActions.getEnumConstants(); Object packet = packetTitle.getConstructor(packetActions, chatBaseComponent, Integer.TYPE, Integer.TYPE, Integer.TYPE).newInstance(actions[3], null, fadeInTime * (ticks ? 1 : 20), stayTime * (ticks ? 1 : 20), fadeOutTime * (ticks ? 1 : 20)); // Send if set if (fadeInTime != -1 && fadeOutTime != -1 && stayTime != -1) sendPacket.invoke(connection, packet); Object serialized; if (!subtitle.equals("")) { // Send subtitle if present serialized = nmsChatSerializer.getConstructor(String.class) .newInstance(subtitleColor + ChatColor.translateAlternateColorCodes('&', subtitle)); packet = packetTitle.getConstructor(packetActions, chatBaseComponent).newInstance(actions[1], serialized); sendPacket.invoke(connection, packet); } // Send title serialized = nmsChatSerializer.getConstructor( String.class).newInstance(titleColor + ChatColor.translateAlternateColorCodes('&', title)); packet = packetTitle.getConstructor(packetActions, chatBaseComponent).newInstance(actions[0], serialized); sendPacket.invoke(connection, packet); } catch (Exception e) { e.printStackTrace(); } } } public void updateTimes(Player player) { if (TitleManager_1_11.packetTitle != null) { try { Object handle = getHandle(player); Object connection = playerConnection.get(handle); Object[] actions = TitleManager_1_11.packetActions.getEnumConstants(); Object packet = TitleManager_1_11.packetTitle.getConstructor( new Class[]{TitleManager_1_11.packetActions, chatBaseComponent, Integer.TYPE, Integer.TYPE, Integer.TYPE}) .newInstance( actions[3], null, this.fadeInTime * (this.ticks ? 1 : 20), this.stayTime * (this.ticks ? 1 : 20), this.fadeOutTime * (this.ticks ? 1 : 20)); if ((this.fadeInTime != -1) && (this.fadeOutTime != -1) && (this.stayTime != -1)) { sendPacket.invoke(connection, packet); } } catch (Exception e) { e.printStackTrace(); } } } public void updateTitle(Player player) { if (TitleManager_1_11.packetTitle != null) { try { Object handle = getHandle(player); Object connection = getField(handle.getClass(), "playerConnection").get(handle); Object[] actions = TitleManager_1_11.packetActions.getEnumConstants(); Method sendPacket = getMethod(connection.getClass(), "sendPacket"); Object serialized = nmsChatSerializer.getConstructor( String.class) .newInstance(titleColor + ChatColor.translateAlternateColorCodes('&', this.title)); Object packet = TitleManager_1_11.packetTitle .getConstructor( new Class[]{TitleManager_1_11.packetActions, chatBaseComponent}).newInstance( actions[0], serialized); sendPacket.invoke(connection, packet); } catch (Exception e) { e.printStackTrace(); } } } public void updateSubtitle(Player player) { if (TitleManager_1_11.packetTitle != null) { try { Object handle = getHandle(player); Object connection = playerConnection.get(handle); Object[] actions = TitleManager_1_11.packetActions.getEnumConstants(); Object serialized = nmsChatSerializer.getConstructor( String.class) .newInstance(subtitleColor + ChatColor.translateAlternateColorCodes('&', this.subtitle)); Object packet = TitleManager_1_11.packetTitle .getConstructor( new Class[]{TitleManager_1_11.packetActions, chatBaseComponent}).newInstance( actions[1], serialized); sendPacket.invoke(connection, packet); } catch (Exception e) { e.printStackTrace(); } } } /** * Broadcast the title to all players */ public void broadcast() { for (Player p : Bukkit.getOnlinePlayers()) { send(p); } } /** * Clear the title * * @param player Player */ public void clearTitle(Player player) { try { // Send timings first Object handle = getHandle(player); Object connection = playerConnection.get(handle); Object[] actions = packetActions.getEnumConstants(); Object packet = packetTitle.getConstructor(packetActions, chatBaseComponent).newInstance(actions[4], null); sendPacket.invoke(connection, packet); } catch (Exception e) { e.printStackTrace(); } } /** * Reset the title settings * * @param player Player */ public void resetTitle(Player player) { try { // Send timings first Object handle = getHandle(player); Object connection = playerConnection.get(handle); Object[] actions = packetActions.getEnumConstants(); Object packet = packetTitle.getConstructor(packetActions, chatBaseComponent).newInstance(actions[5], null); sendPacket.invoke(connection, packet); } catch (Exception e) { e.printStackTrace(); } } private Class<?> getPrimitiveType(Class<?> clazz) { return CORRESPONDING_TYPES.containsKey(clazz) ? CORRESPONDING_TYPES .get(clazz) : clazz; } private 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; } private 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; } private Object getHandle(Player player) { try { return methodPlayerGetHandle.invoke(player); } catch (Exception e) { e.printStackTrace(); return null; } } private 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; } private String getVersion() { String name = Bukkit.getServer().getClass().getPackage().getName(); String version = name.substring(name.lastIndexOf('.') + 1) + "."; return version; } private 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; } private Class<?> getOBCClass(String className) { String fullName = "org.bukkit.craftbukkit." + getVersion() + className; Class<?> clazz = null; try { clazz = Class.forName(fullName); } catch (Exception e) { e.printStackTrace(); } return clazz; } private Field getField(Class<?> clazz, String name) { try { Field field = clazz.getDeclaredField(name); field.setAccessible(true); return field; } catch (Exception e) { e.printStackTrace(); return null; } } private 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; } private 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; } public ChatColor getTitleColor() { return titleColor; } public ChatColor getSubtitleColor() { return subtitleColor; } public int getFadeInTime() { return fadeInTime; } public int getFadeOutTime() { return fadeOutTime; } public int getStayTime() { return stayTime; } public boolean isTicks() { return ticks; } }