├── src └── main │ ├── resources │ ├── plugin.yml │ └── config.yml │ └── java │ └── com │ └── connorlinfoot │ └── titleapi │ ├── InternalTitleAPI.java │ ├── BukkitTitleAPI.java │ ├── InternalAPIMapping.java │ ├── MinecraftVersion.java │ ├── TabTitleSendEvent.java │ ├── TitleSendEvent.java │ ├── TitleAPI.java │ ├── CLUpdate.java │ └── ReflectionTitleAPI.java ├── .github └── workflows │ └── maven.yml ├── README.md └── pom.xml /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: TitleAPI 2 | description: Simple to use library for sending Titles to players in Bukkit. 3 | main: com.connorlinfoot.titleapi.TitleAPI 4 | version: ${project.version} 5 | api-version: 1.13 -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | Title On Join: false 2 | Title Message: "Welcome" 3 | Subtitle Message: "%player% to my server" 4 | Tab Header Enabled: false 5 | Tab Header Message: "My Server" 6 | Tab Footer Message: "My IP" 7 | Check for Updates: true -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/InternalTitleAPI.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | interface InternalTitleAPI { 6 | 7 | void sendTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut); 8 | 9 | default void clearTitle(Player player) { 10 | sendTitle(player, "", "", 0, 0, 0); 11 | } 12 | 13 | void sendTabTitle(Player player, String header, String footer); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/BukkitTitleAPI.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | class BukkitTitleAPI implements InternalTitleAPI { 6 | 7 | BukkitTitleAPI() { 8 | } 9 | 10 | @Override 11 | public void sendTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut) { 12 | player.sendTitle(title, subtitle, fadeIn, stay, fadeOut); 13 | } 14 | 15 | @Override 16 | public void sendTabTitle(Player player, String header, String footer) { 17 | player.setPlayerListHeaderFooter(header, footer); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 8 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '8' 21 | distribution: 'adopt' 22 | - name: Build with Maven 23 | run: mvn -B package --file pom.xml 24 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/InternalAPIMapping.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import java.util.logging.Level; 4 | 5 | enum InternalAPIMapping { 6 | REFLECTION(ReflectionTitleAPI.class, MinecraftVersion.v1_12), 7 | BUKKIT(BukkitTitleAPI.class, MinecraftVersion.v1_17), 8 | ; 9 | private final Class apiClass; 10 | private final MinecraftVersion maxVersion; 11 | 12 | InternalAPIMapping(Class apiClass, MinecraftVersion maxVersion) { 13 | this.apiClass = apiClass; 14 | this.maxVersion = maxVersion; 15 | } 16 | 17 | static InternalTitleAPI create() { 18 | MinecraftVersion version = MinecraftVersion.get(); 19 | for (InternalAPIMapping thisOne : values()) { 20 | if (version.isLessThanOrEqualTo(thisOne.maxVersion)) { 21 | try { 22 | return thisOne.apiClass.newInstance(); 23 | } catch (InstantiationException | IllegalAccessException e) { 24 | TitleAPI.instance.getLogger().log(Level.WARNING, "Failed to create InternalTitleAPI", e); 25 | } 26 | } 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TitleAPI 2 | ===================== 3 | 4 | [![Java CI with Maven](https://github.com/ConnorLinfoot/TitleAPI/actions/workflows/maven.yml/badge.svg)](https://github.com/ConnorLinfoot/TitleAPI/actions/workflows/maven.yml) 5 | [![](https://jitpack.io/v/ConnorLinfoot/TitleAPI.svg)](https://jitpack.io/#ConnorLinfoot/TitleAPI) 6 | 7 | Simple to use library for sending Titles to players in Bukkit, supporting 1.8 - 1.17. 8 | 9 | ## Use Bukkit APIs 10 | 11 | While this library does work with even the latest version of Bukkit, it is heavily recommended when possible to use the 12 | built-in APIs that have existed in Bukkit for over 5 years now. 13 | 14 | ## Maven 15 | 16 | This library can be found on [JitPack](https://jitpack.io/#ConnorLinfoot/TitleAPI) for easy integration into maven 17 | projects. 18 | 19 | ```xml 20 | 21 | 22 | jitpack.io 23 | https://jitpack.io 24 | 25 | 26 | 27 | 28 | 29 | com.github.ConnorLinfoot 30 | TitleAPI 31 | 1.8.1 32 | 33 | 34 | ``` 35 | 36 | Spigot Page/More Info: http://www.spigotmc.org/resources/titleapi.1325/ 37 | 38 | Release Notes: http://www.spigotmc.org/resources/titleapi.1325/updates 39 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/MinecraftVersion.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.Bukkit; 4 | 5 | enum MinecraftVersion { 6 | v1_8("1_8"), 7 | v1_9("1_9"), 8 | v1_10("1_10"), 9 | v1_11("1_11"), 10 | v1_12("1_12"), 11 | v1_13("1_13"), 12 | v1_14("1_14"), 13 | v1_15("1_15"), 14 | v1_16("1_16"), 15 | v1_17("1_17"), 16 | ; 17 | private final String key; 18 | 19 | MinecraftVersion(String key) { 20 | this.key = key; 21 | } 22 | 23 | public boolean isGreaterThanOrEqualTo(MinecraftVersion other) { 24 | return ordinal() >= other.ordinal(); 25 | } 26 | 27 | public boolean isLessThanOrEqualTo(MinecraftVersion other) { 28 | return ordinal() <= other.ordinal(); 29 | } 30 | 31 | private static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; 32 | private static final MinecraftVersion MINECRAFT_VERSION = MinecraftVersion.build(); 33 | 34 | private static MinecraftVersion build() { 35 | for (MinecraftVersion k : MinecraftVersion.values()) { 36 | if (MinecraftVersion.VERSION.contains(k.key)) { 37 | return k; 38 | } 39 | } 40 | return v1_17; // Just return latest for unknown 41 | } 42 | 43 | public static MinecraftVersion get() { 44 | return MINECRAFT_VERSION; 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/TabTitleSendEvent.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Event; 5 | import org.bukkit.event.HandlerList; 6 | 7 | public class TabTitleSendEvent extends Event { 8 | private static final HandlerList handlers = new HandlerList(); 9 | private final Player player; 10 | private String header; 11 | private String footer; 12 | private boolean cancelled = false; 13 | 14 | TabTitleSendEvent(Player player, String header, String footer) { 15 | this.player = player; 16 | this.header = header; 17 | this.footer = footer; 18 | } 19 | 20 | @Override 21 | public HandlerList getHandlers() { 22 | return handlers; 23 | } 24 | 25 | public static HandlerList getHandlerList() { 26 | return handlers; 27 | } 28 | 29 | public Player getPlayer() { 30 | return player; 31 | } 32 | 33 | public String getHeader() { 34 | return header; 35 | } 36 | 37 | public void setHeader(String header) { 38 | this.header = header; 39 | } 40 | 41 | public String getFooter() { 42 | return footer; 43 | } 44 | 45 | public void setFooter(String footer) { 46 | this.footer = footer; 47 | } 48 | 49 | public boolean isCancelled() { 50 | return cancelled; 51 | } 52 | 53 | public void setCancelled(boolean cancelled) { 54 | this.cancelled = cancelled; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/TitleSendEvent.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Event; 5 | import org.bukkit.event.HandlerList; 6 | 7 | public class TitleSendEvent extends Event { 8 | private static final HandlerList handlers = new HandlerList(); 9 | private final Player player; 10 | private String title; 11 | private String subtitle; 12 | private boolean cancelled = false; 13 | 14 | TitleSendEvent(Player player, String title, String subtitle) { 15 | this.player = player; 16 | this.title = title; 17 | this.subtitle = subtitle; 18 | } 19 | 20 | @Override 21 | public HandlerList getHandlers() { 22 | return handlers; 23 | } 24 | 25 | public static HandlerList getHandlerList() { 26 | return handlers; 27 | } 28 | 29 | public Player getPlayer() { 30 | return player; 31 | } 32 | 33 | public String getTitle() { 34 | return title; 35 | } 36 | 37 | public void setTitle(String title) { 38 | this.title = title; 39 | } 40 | 41 | public String getSubtitle() { 42 | return subtitle; 43 | } 44 | 45 | public void setSubtitle(String subtitle) { 46 | this.subtitle = subtitle; 47 | } 48 | 49 | public boolean isCancelled() { 50 | return cancelled; 51 | } 52 | 53 | public void setCancelled(boolean cancelled) { 54 | this.cancelled = cancelled; 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.connorlinfoot 8 | TitleAPI 9 | 1.8.1 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | clean package install 17 | TitleAPI-${project.version} 18 | ${basedir}/src/main/java 19 | 20 | 21 | src/main/resources 22 | true 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-compiler-plugin 30 | 3.0 31 | 32 | 1.8 33 | 1.8 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | spigot-repo 43 | https://hub.spigotmc.org/nexus/content/groups/public/ 44 | 45 | 46 | 47 | 48 | org.bukkit 49 | bukkit 50 | 1.13.1-R0.1-SNAPSHOT 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/TitleAPI.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.Server; 6 | import org.bukkit.command.ConsoleCommandSender; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.player.PlayerJoinEvent; 12 | import org.bukkit.plugin.java.JavaPlugin; 13 | 14 | import java.lang.reflect.Constructor; 15 | import java.lang.reflect.Field; 16 | import java.util.Optional; 17 | import java.util.logging.Level; 18 | 19 | 20 | public class TitleAPI extends JavaPlugin implements Listener { 21 | static TitleAPI instance; 22 | 23 | @Deprecated 24 | public static void sendTitle(Player player, int fadeIn, int stay, int fadeOut, String message) { 25 | sendTitle(player, fadeIn, stay, fadeOut, message, null); 26 | } 27 | 28 | @Deprecated 29 | public static void sendSubtitle(Player player, int fadeIn, int stay, int fadeOut, String message) { 30 | sendTitle(player, fadeIn, stay, fadeOut, null, message); 31 | } 32 | 33 | @Deprecated 34 | public static void sendFullTitle(Player player, int fadeIn, int stay, int fadeOut, String title, String subtitle) { 35 | sendTitle(player, fadeIn, stay, fadeOut, title, subtitle); 36 | } 37 | 38 | public static void sendTitle(Player player, int fadeIn, int stay, int fadeOut, String title, String subtitle) { 39 | sendTitle(player, title, subtitle, fadeIn, stay, fadeOut); 40 | } 41 | 42 | public static void sendTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut) { 43 | if (instance == null) { 44 | Bukkit.getLogger().log(Level.WARNING, "TitleAPI was called before being enabled! Please make sure to add TitleAPI as a dependency to your plugin.yml", new RuntimeException("Too early")); 45 | return; 46 | } 47 | 48 | InternalTitleAPI internalApi = instance.getInternalApi().orElseThrow(() -> new RuntimeException("No internal API, Unsupported version?")); 49 | 50 | TitleSendEvent titleSendEvent = new TitleSendEvent(player, title, subtitle); 51 | Bukkit.getPluginManager().callEvent(titleSendEvent); 52 | if (titleSendEvent.isCancelled()) { 53 | return; 54 | } 55 | 56 | internalApi.sendTitle(player, title, subtitle, fadeIn, stay, fadeOut); 57 | } 58 | 59 | public static void clearTitle(Player player) { 60 | if (instance == null) { 61 | Bukkit.getLogger().log(Level.WARNING, "TitleAPI was called before being enabled! Please make sure to add TitleAPI as a dependency to your plugin.yml", new RuntimeException("Too early")); 62 | return; 63 | } 64 | 65 | InternalTitleAPI internalApi = instance.getInternalApi().orElseThrow(() -> new RuntimeException("No internal API, Unsupported version?")); 66 | internalApi.clearTitle(player); 67 | } 68 | 69 | public static void sendTabTitle(Player player, String header, String footer) { 70 | if (instance == null) { 71 | Bukkit.getLogger().log(Level.WARNING, "TitleAPI was called before being enabled! Please make sure to add TitleAPI as a dependency to your plugin.yml", new RuntimeException("Too early")); 72 | return; 73 | } 74 | 75 | InternalTitleAPI internalApi = instance.getInternalApi().orElseThrow(() -> new RuntimeException("No internal API, Unsupported version?")); 76 | internalApi.sendTabTitle(player, header, footer); 77 | } 78 | 79 | private InternalTitleAPI internalApi; 80 | 81 | @Override 82 | public void onEnable() { 83 | instance = this; 84 | getConfig().options().copyDefaults(true); 85 | saveConfig(); 86 | 87 | this.internalApi = InternalAPIMapping.create(); 88 | Bukkit.getPluginManager().registerEvents(this, this); 89 | 90 | if (getConfig().getBoolean("Check for Updates")) { 91 | CLUpdate updater = new CLUpdate(this); 92 | Bukkit.getPluginManager().registerEvents(updater, this); 93 | } 94 | } 95 | 96 | Optional getInternalApi() { 97 | return Optional.ofNullable(internalApi); 98 | } 99 | 100 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 101 | public void onPlayerJoin(PlayerJoinEvent event) { 102 | if (getConfig().getBoolean("Title On Join")) { 103 | sendTitle(event.getPlayer(), 20, 50, 20, getConfig().getString("Title Message"), getConfig().getString("Subtitle Message")); 104 | } 105 | 106 | if (getConfig().getBoolean("Tab Header Enabled")) { 107 | sendTabTitle(event.getPlayer(), getConfig().getString("Tab Header Message"), getConfig().getString("Tab Footer Message")); 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/CLUpdate.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.event.player.PlayerJoinEvent; 8 | import org.bukkit.plugin.Plugin; 9 | import org.bukkit.plugin.java.JavaPlugin; 10 | import org.json.simple.JSONObject; 11 | import org.json.simple.parser.JSONParser; 12 | import org.json.simple.parser.ParseException; 13 | 14 | import java.io.DataInputStream; 15 | import java.io.DataOutputStream; 16 | import java.io.IOException; 17 | import java.net.HttpURLConnection; 18 | import java.net.URL; 19 | 20 | class CLUpdate implements Listener { 21 | private CLUpdate.UpdateResult result = CLUpdate.UpdateResult.DISABLED; 22 | private String version; 23 | private Plugin plugin; 24 | private String message = null; 25 | private String pluginMessage = null; 26 | private String updateMessage = null; 27 | private boolean updateAvailable = false; 28 | 29 | public enum UpdateResult { 30 | NO_UPDATE, 31 | DISABLED, 32 | UPDATE_AVAILABLE 33 | } 34 | 35 | CLUpdate(JavaPlugin plugin) { 36 | this.plugin = plugin; 37 | Bukkit.getScheduler().runTaskAsynchronously(plugin, this::doCheck); 38 | } 39 | 40 | private void doCheck() { 41 | String data; 42 | String url = "http://api.linfoot.dev/v1/resource/release/" + plugin.getDescription().getName().toLowerCase() + "/"; 43 | try { 44 | data = doCurl(url); 45 | } catch (IOException e) { 46 | return; 47 | } 48 | 49 | JSONParser jsonParser = new JSONParser(); 50 | try { 51 | JSONObject obj = (JSONObject) jsonParser.parse(data); 52 | if (obj.get("version") != null) { 53 | String newestVersion = (String) obj.get("version"); 54 | String currentVersion = plugin.getDescription().getVersion().replaceAll("-SNAPSHOT-", "."); // Changes 4.0.0-SNAPSHOT-4 to 4.0.0.4 55 | if (Integer.parseInt(newestVersion.replace(".", "")) > Integer.parseInt(currentVersion.replace(".", ""))) { 56 | result = UpdateResult.UPDATE_AVAILABLE; 57 | version = (String) obj.get("version"); 58 | } else { 59 | result = UpdateResult.NO_UPDATE; 60 | } 61 | if (obj.containsKey("message")) { 62 | this.message = ChatColor.translateAlternateColorCodes('&', (String) obj.get("message")); 63 | Bukkit.getConsoleSender().sendMessage(ChatColor.translateAlternateColorCodes('&', (String) obj.get("message"))); 64 | } 65 | } 66 | } catch (ParseException e) { 67 | return; 68 | } 69 | 70 | Bukkit.getScheduler().runTask(plugin, this::handleResult); 71 | } 72 | 73 | public String getVersion() { 74 | return version; 75 | } 76 | 77 | public String doCurl(String urlString) throws IOException { 78 | URL url = new URL(urlString); 79 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 80 | con.setRequestMethod("POST"); 81 | con.setInstanceFollowRedirects(true); 82 | con.setDoOutput(true); 83 | con.setDoInput(true); 84 | DataOutputStream output = new DataOutputStream(con.getOutputStream()); 85 | output.close(); 86 | DataInputStream input = new DataInputStream(con.getInputStream()); 87 | int c; 88 | StringBuilder resultBuf = new StringBuilder(); 89 | while ((c = input.read()) != -1) { 90 | resultBuf.append((char) c); 91 | } 92 | input.close(); 93 | return resultBuf.toString(); 94 | } 95 | 96 | public String getMessage() { 97 | return message; 98 | } 99 | 100 | public void handleResult() { 101 | if (getMessage() != null) { 102 | pluginMessage = getMessage(); 103 | } 104 | 105 | switch (result) { 106 | default: 107 | case NO_UPDATE: 108 | updateAvailable = false; 109 | updateMessage = "No update was found, you are running the latest version."; 110 | break; 111 | case DISABLED: 112 | updateAvailable = false; 113 | updateMessage = "You currently have update checks disabled"; 114 | break; 115 | case UPDATE_AVAILABLE: 116 | updateAvailable = true; 117 | updateMessage = "An update for " + plugin.getDescription().getName() + " is available, new version is " + getVersion() + ". Your installed version is " + plugin.getDescription().getVersion() + ".\nPlease update to the latest version :)"; 118 | break; 119 | } 120 | 121 | plugin.getLogger().info(updateMessage); 122 | } 123 | 124 | @EventHandler 125 | public void onPlayerJoin(PlayerJoinEvent event) { 126 | if (updateAvailable && event.getPlayer().isOp()) { 127 | event.getPlayer().sendMessage(updateMessage); 128 | } 129 | if (pluginMessage != null && event.getPlayer().isOp()) { 130 | event.getPlayer().sendMessage(pluginMessage); 131 | } 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /src/main/java/com/connorlinfoot/titleapi/ReflectionTitleAPI.java: -------------------------------------------------------------------------------- 1 | package com.connorlinfoot.titleapi; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.util.logging.Level; 10 | 11 | class ReflectionTitleAPI implements InternalTitleAPI { 12 | 13 | ReflectionTitleAPI() { 14 | 15 | } 16 | 17 | private void sendPacket(Player player, Object packet) { 18 | try { 19 | Object handle = player.getClass().getMethod("getHandle").invoke(player); 20 | Object playerConnection = handle.getClass().getField("playerConnection").get(handle); 21 | playerConnection.getClass().getMethod("sendPacket", getNMSClass("Packet")).invoke(playerConnection, packet); 22 | } catch (Exception e) { 23 | TitleAPI.instance.getLogger().log(Level.WARNING, "Failed to send packet", e); 24 | } 25 | } 26 | 27 | private Class getNMSClass(String name) { 28 | String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; 29 | try { 30 | return Class.forName("net.minecraft.server." + version + "." + name); 31 | } catch (ClassNotFoundException e) { 32 | return null; 33 | } 34 | } 35 | 36 | @Override 37 | public void sendTitle(Player player, String title, String subtitle, int fadeIn, int stay, int fadeOut) { 38 | try { 39 | Object e; 40 | Object chatTitle; 41 | Object chatSubtitle; 42 | Constructor subtitleConstructor; 43 | Object titlePacket; 44 | Object subtitlePacket; 45 | 46 | if (title != null) { 47 | title = ChatColor.translateAlternateColorCodes('&', title); 48 | title = title.replaceAll("%player%", player.getDisplayName()); 49 | // Times packets 50 | e = getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("TIMES").get(null); 51 | chatTitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", new Class[]{String.class}).invoke((Object) null, new Object[]{"{\"text\":\"" + title + "\"}"}); 52 | subtitleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(new Class[]{getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent"), Integer.TYPE, Integer.TYPE, Integer.TYPE}); 53 | titlePacket = subtitleConstructor.newInstance(new Object[]{e, chatTitle, fadeIn, stay, fadeOut}); 54 | sendPacket(player, titlePacket); 55 | 56 | e = getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("TITLE").get(null); 57 | chatTitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", new Class[]{String.class}).invoke((Object) null, new Object[]{"{\"text\":\"" + title + "\"}"}); 58 | subtitleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(new Class[]{getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent")}); 59 | titlePacket = subtitleConstructor.newInstance(new Object[]{e, chatTitle}); 60 | sendPacket(player, titlePacket); 61 | } 62 | 63 | if (subtitle != null) { 64 | subtitle = ChatColor.translateAlternateColorCodes('&', subtitle); 65 | subtitle = subtitle.replaceAll("%player%", player.getDisplayName()); 66 | // Times packets 67 | e = getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("TIMES").get(null); 68 | chatSubtitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", new Class[]{String.class}).invoke((Object) null, new Object[]{"{\"text\":\"" + title + "\"}"}); 69 | subtitleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(new Class[]{getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent"), Integer.TYPE, Integer.TYPE, Integer.TYPE}); 70 | subtitlePacket = subtitleConstructor.newInstance(new Object[]{e, chatSubtitle, fadeIn, stay, fadeOut}); 71 | sendPacket(player, subtitlePacket); 72 | 73 | e = getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0].getField("SUBTITLE").get(null); 74 | chatSubtitle = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", new Class[]{String.class}).invoke((Object) null, new Object[]{"{\"text\":\"" + subtitle + "\"}"}); 75 | subtitleConstructor = getNMSClass("PacketPlayOutTitle").getConstructor(new Class[]{getNMSClass("PacketPlayOutTitle").getDeclaredClasses()[0], getNMSClass("IChatBaseComponent"), Integer.TYPE, Integer.TYPE, Integer.TYPE}); 76 | subtitlePacket = subtitleConstructor.newInstance(new Object[]{e, chatSubtitle, fadeIn, stay, fadeOut}); 77 | sendPacket(player, subtitlePacket); 78 | } 79 | } catch (Exception e) { 80 | TitleAPI.instance.getLogger().log(Level.WARNING, "Failed to handle reflection", e); 81 | } 82 | } 83 | 84 | @Override 85 | public void sendTabTitle(Player player, String header, String footer) { 86 | if (header == null) header = ""; 87 | header = ChatColor.translateAlternateColorCodes('&', header); 88 | 89 | if (footer == null) footer = ""; 90 | footer = ChatColor.translateAlternateColorCodes('&', footer); 91 | 92 | TabTitleSendEvent tabTitleSendEvent = new TabTitleSendEvent(player, header, footer); 93 | Bukkit.getPluginManager().callEvent(tabTitleSendEvent); 94 | if (tabTitleSendEvent.isCancelled()) 95 | return; 96 | 97 | header = header.replaceAll("%player%", player.getDisplayName()); 98 | footer = footer.replaceAll("%player%", player.getDisplayName()); 99 | 100 | try { 101 | Object tabHeader = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\":\"" + header + "\"}"); 102 | Object tabFooter = getNMSClass("IChatBaseComponent").getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\":\"" + footer + "\"}"); 103 | Constructor titleConstructor = getNMSClass("PacketPlayOutPlayerListHeaderFooter").getConstructor(); 104 | Object packet = titleConstructor.newInstance(); 105 | try { 106 | Field aField = packet.getClass().getDeclaredField("a"); 107 | aField.setAccessible(true); 108 | aField.set(packet, tabHeader); 109 | Field bField = packet.getClass().getDeclaredField("b"); 110 | bField.setAccessible(true); 111 | bField.set(packet, tabFooter); 112 | } catch (Exception e) { 113 | Field aField = packet.getClass().getDeclaredField("header"); 114 | aField.setAccessible(true); 115 | aField.set(packet, tabHeader); 116 | Field bField = packet.getClass().getDeclaredField("footer"); 117 | bField.setAccessible(true); 118 | bField.set(packet, tabFooter); 119 | } 120 | sendPacket(player, packet); 121 | } catch (Exception ex) { 122 | TitleAPI.instance.getLogger().log(Level.WARNING, "Failed to handle reflection", ex); 123 | } 124 | } 125 | } 126 | --------------------------------------------------------------------------------