├── 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 extends InternalTitleAPI> apiClass;
10 | private final MinecraftVersion maxVersion;
11 |
12 | InternalAPIMapping(Class extends InternalTitleAPI> 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 | [](https://github.com/ConnorLinfoot/TitleAPI/actions/workflows/maven.yml)
5 | [](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 |
--------------------------------------------------------------------------------