expansions = PlaceholderAPI.getExpansions();
+
+ if (expansions.isEmpty()) {
+ return;
+ }
+
+ for (PlaceholderExpansion ex : expansions) {
+ if (ex instanceof Cleanable) {
+ ((Cleanable) ex).cleanup(e.getPlayer());
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/clip/placeholderapi/ServerLoadEventListener.java b/src/main/java/me/clip/placeholderapi/listeners/ServerLoadEventListener.java
similarity index 90%
rename from src/main/java/me/clip/placeholderapi/ServerLoadEventListener.java
rename to src/main/java/me/clip/placeholderapi/listeners/ServerLoadEventListener.java
index 0ad80cc..b1f38f7 100644
--- a/src/main/java/me/clip/placeholderapi/ServerLoadEventListener.java
+++ b/src/main/java/me/clip/placeholderapi/listeners/ServerLoadEventListener.java
@@ -18,8 +18,11 @@
*
*
*/
-package me.clip.placeholderapi;
+package me.clip.placeholderapi.listeners;
+import me.clip.placeholderapi.PlaceholderAPI;
+import me.clip.placeholderapi.PlaceholderAPIPlugin;
+import me.clip.placeholderapi.PlaceholderHook;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -38,11 +41,12 @@ public class ServerLoadEventListener implements Listener {
/**
* This method will be called when the server is first loaded
- *
+ *
* The goal of the method is to register all the expansions as soon as possible
* especially before players can join
- *
+ *
* This will ensure no issues with expanions and hooks.
+ *
* @param e the server load event
*/
@EventHandler
diff --git a/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java b/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java
index f3f56af..afafb0d 100644
--- a/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java
+++ b/src/main/java/me/clip/placeholderapi/updatechecker/UpdateChecker.java
@@ -35,83 +35,83 @@ import java.net.URL;
public class UpdateChecker implements Listener {
- private final int RESOURCE_ID = 6245;
- private final PlaceholderAPIPlugin plugin;
- private String spigotVersion;
- private final String pluginVersion;
- private boolean updateAvailable;
+ private final int RESOURCE_ID = 6245;
+ private final PlaceholderAPIPlugin plugin;
+ private final String pluginVersion;
+ private String spigotVersion;
+ private boolean updateAvailable;
- public UpdateChecker(PlaceholderAPIPlugin i) {
- plugin = i;
- pluginVersion = i.getDescription().getVersion();
- }
-
- public boolean hasUpdateAvailable() {
- return updateAvailable;
- }
-
- public String getSpigotVersion() {
- return spigotVersion;
- }
-
- public void fetch() {
- Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
- try {
- HttpsURLConnection con = (HttpsURLConnection) new URL(
- "https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID).openConnection();
- con.setRequestMethod("GET");
- spigotVersion = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine();
- } catch (Exception ex) {
- plugin.getLogger().info("Failed to check for updates on spigot.");
- return;
- }
-
- if (spigotVersion == null || spigotVersion.isEmpty()) {
- return;
- }
-
- updateAvailable = spigotIsNewer();
-
- if (!updateAvailable) {
- return;
- }
-
- Bukkit.getScheduler().runTask(plugin, () -> {
- plugin.getLogger()
- .info("An update for PlaceholderAPI (v" + getSpigotVersion() + ") is available at:");
- plugin.getLogger()
- .info("https://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID + "/");
- Bukkit.getPluginManager().registerEvents(this, plugin);
- });
- });
- }
-
- private boolean spigotIsNewer() {
- if (spigotVersion == null || spigotVersion.isEmpty()) {
- return false;
+ public UpdateChecker(PlaceholderAPIPlugin i) {
+ plugin = i;
+ pluginVersion = i.getDescription().getVersion();
}
- String plV = toReadable(pluginVersion);
- String spV = toReadable(spigotVersion);
- return plV.compareTo(spV) < 0;
- }
-
- private String toReadable(String version) {
- if (version.contains("-DEV-")) {
- version = version.split("-DEV-")[0];
+ public boolean hasUpdateAvailable() {
+ return updateAvailable;
}
- return version.replaceAll("\\.", "");
- }
-
- @EventHandler(priority = EventPriority.MONITOR)
- public void onJoin(PlayerJoinEvent e) {
- if (e.getPlayer().hasPermission("placeholderapi.updatenotify")) {
- Msg.msg(e.getPlayer(),
- "&bAn update for &fPlaceholder&7API &e(&fPlaceholder&7API &fv" + getSpigotVersion()
- + "&e)"
- , "&bis available at &ehttps://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID
- + "/");
+ public String getSpigotVersion() {
+ return spigotVersion;
+ }
+
+ public void fetch() {
+ Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
+ try {
+ HttpsURLConnection con = (HttpsURLConnection) new URL(
+ "https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID).openConnection();
+ con.setRequestMethod("GET");
+ spigotVersion = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine();
+ } catch (Exception ex) {
+ plugin.getLogger().info("Failed to check for updates on spigot.");
+ return;
+ }
+
+ if (spigotVersion == null || spigotVersion.isEmpty()) {
+ return;
+ }
+
+ updateAvailable = spigotIsNewer();
+
+ if (!updateAvailable) {
+ return;
+ }
+
+ Bukkit.getScheduler().runTask(plugin, () -> {
+ plugin.getLogger()
+ .info("An update for PlaceholderAPI (v" + getSpigotVersion() + ") is available at:");
+ plugin.getLogger()
+ .info("https://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID + "/");
+ Bukkit.getPluginManager().registerEvents(this, plugin);
+ });
+ });
+ }
+
+ private boolean spigotIsNewer() {
+ if (spigotVersion == null || spigotVersion.isEmpty()) {
+ return false;
+ }
+
+ String plV = toReadable(pluginVersion);
+ String spV = toReadable(spigotVersion);
+ return plV.compareTo(spV) < 0;
+ }
+
+ private String toReadable(String version) {
+ if (version.contains("-DEV-")) {
+ version = version.split("-DEV-")[0];
+ }
+
+ return version.replaceAll("\\.", "");
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onJoin(PlayerJoinEvent e) {
+ if (e.getPlayer().hasPermission("placeholderapi.updatenotify")) {
+ Msg.msg(e.getPlayer(),
+ "&bAn update for &fPlaceholder&7API &e(&fPlaceholder&7API &fv" + getSpigotVersion()
+ + "&e)"
+ , "&bis available at &ehttps://www.spigotmc.org/resources/placeholderapi." + RESOURCE_ID
+ + "/");
+ }
}
- }
}
diff --git a/src/main/java/me/clip/placeholderapi/util/Constants.java b/src/main/java/me/clip/placeholderapi/util/Constants.java
new file mode 100644
index 0000000..d911793
--- /dev/null
+++ b/src/main/java/me/clip/placeholderapi/util/Constants.java
@@ -0,0 +1,9 @@
+package me.clip.placeholderapi.util;
+
+public class Constants {
+ public static final String ADMIN_PERMISSION = "placeholderapi.admin";
+ public static final String ECLOUD_PERMISSION = "placeholderapi.ecloud";
+ public static final String INFO_PERMISSION = "placeholderapi.info";
+ public static final String LIST_PERMISSION = "placeholderapi.list";
+ public static final String RELOAD_PERMISSION = "placeholderapi.reload";
+}
diff --git a/src/main/java/me/clip/placeholderapi/util/FileUtil.java b/src/main/java/me/clip/placeholderapi/util/FileUtil.java
index 0660337..84eb613 100644
--- a/src/main/java/me/clip/placeholderapi/util/FileUtil.java
+++ b/src/main/java/me/clip/placeholderapi/util/FileUtil.java
@@ -33,76 +33,76 @@ import java.util.jar.JarInputStream;
public class FileUtil {
- public static List> getClasses(String folder, Class> type) {
- return getClasses(folder, null, type);
- }
+ public static List> getClasses(String folder, Class> type) {
+ return getClasses(folder, null, type);
+ }
- public static List> getClasses(String folder, String fileName, Class> type) {
- List> list = new ArrayList<>();
+ public static List> getClasses(String folder, String fileName, Class> type) {
+ List> list = new ArrayList<>();
+
+ try {
+ File f = new File(PlaceholderAPIPlugin.getInstance().getDataFolder(), folder);
+ if (!f.exists()) {
+ return list;
+ }
+
+ FilenameFilter fileNameFilter = (dir, name) -> {
+ if (fileName != null) {
+ return name.endsWith(".jar") && name.replace(".jar", "")
+ .equalsIgnoreCase(fileName.replace(".jar", ""));
+ }
+
+ return name.endsWith(".jar");
+ };
+
+ File[] jars = f.listFiles(fileNameFilter);
+ if (jars == null) {
+ return list;
+ }
+
+ for (File file : jars) {
+ list = gather(file.toURI().toURL(), list, type);
+ }
+
+ return list;
+ } catch (Throwable t) {
+ }
+
+ return null;
+ }
+
+ private static List> gather(URL jar, List> list, Class> clazz) {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+
+ try (URLClassLoader cl = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader());
+ JarInputStream jis = new JarInputStream(jar.openStream())) {
+
+ while (true) {
+ JarEntry j = jis.getNextJarEntry();
+ if (j == null) {
+ break;
+ }
+
+ String name = j.getName();
+ if (name == null || name.isEmpty()) {
+ continue;
+ }
+
+ if (name.endsWith(".class")) {
+ name = name.replace("/", ".");
+ String cname = name.substring(0, name.lastIndexOf(".class"));
+
+ Class> c = cl.loadClass(cname);
+ if (clazz.isAssignableFrom(c)) {
+ list.add(c);
+ }
+ }
+ }
+ } catch (Throwable t) {
+ }
- try {
- File f = new File(PlaceholderAPIPlugin.getInstance().getDataFolder(), folder);
- if (!f.exists()) {
return list;
- }
-
- FilenameFilter fileNameFilter = (dir, name) -> {
- if (fileName != null) {
- return name.endsWith(".jar") && name.replace(".jar", "")
- .equalsIgnoreCase(fileName.replace(".jar", ""));
- }
-
- return name.endsWith(".jar");
- };
-
- File[] jars = f.listFiles(fileNameFilter);
- if (jars == null) {
- return list;
- }
-
- for (File file : jars) {
- list = gather(file.toURI().toURL(), list, type);
- }
-
- return list;
- } catch (Throwable t) {
}
-
- return null;
- }
-
- private static List> gather(URL jar, List> list, Class> clazz) {
- if (list == null) {
- list = new ArrayList<>();
- }
-
- try (URLClassLoader cl = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader());
- JarInputStream jis = new JarInputStream(jar.openStream())) {
-
- while (true) {
- JarEntry j = jis.getNextJarEntry();
- if (j == null) {
- break;
- }
-
- String name = j.getName();
- if (name == null || name.isEmpty()) {
- continue;
- }
-
- if (name.endsWith(".class")) {
- name = name.replace("/", ".");
- String cname = name.substring(0, name.lastIndexOf(".class"));
-
- Class> c = cl.loadClass(cname);
- if (clazz.isAssignableFrom(c)) {
- list.add(c);
- }
- }
- }
- } catch (Throwable t) {
- }
-
- return list;
- }
}
diff --git a/src/main/java/me/clip/placeholderapi/util/JSONMessage.java b/src/main/java/me/clip/placeholderapi/util/JSONMessage.java
new file mode 100644
index 0000000..cf62c34
--- /dev/null
+++ b/src/main/java/me/clip/placeholderapi/util/JSONMessage.java
@@ -0,0 +1,985 @@
+package me.clip.placeholderapi.util;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.Vector;
+
+/**
+ * This is a complete JSON message builder class. To create a new JSONMessage do
+ * {@link #create(String)}
+ *
+ * @author Rayzr
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class JSONMessage {
+ private static final BiMap stylesToNames;
+
+ static {
+ ImmutableBiMap.Builder builder = ImmutableBiMap.builder();
+ for (final ChatColor style : ChatColor.values()) {
+ if (!style.isFormat()) {
+ continue;
+ }
+
+ String styleName;
+ switch (style) {
+ case MAGIC:
+ styleName = "obfuscated";
+ break;
+ case UNDERLINE:
+ styleName = "underlined";
+ break;
+ default:
+ styleName = style.name().toLowerCase();
+ break;
+ }
+
+ builder.put(style, styleName);
+ }
+ stylesToNames = builder.build();
+ }
+
+
+ private final List parts = new ArrayList<>();
+ private int centeringStartIndex = -1;
+
+ /**
+ * Creates a new {@link JSONMessage} object
+ *
+ * @param text The text to start with
+ */
+ private JSONMessage(String text) {
+ parts.add(new MessagePart(text));
+ }
+
+ /**
+ * Creates a new {@link JSONMessage} object
+ *
+ * @param text The text to start with
+ * @return A new {@link JSONMessage} object
+ */
+ public static JSONMessage create(String text) {
+ return new JSONMessage(text);
+ }
+
+ /**
+ * Creates a new {@link JSONMessage} object
+ *
+ * @return A new {@link JSONMessage} object
+ */
+ public static JSONMessage create() {
+ return create("");
+ }
+
+ /**
+ * Sends an action bar message
+ *
+ * @param message The message to send
+ * @param players The players you want to send it to
+ */
+ public static void actionbar(String message, Player... players) {
+ ReflectionHelper.sendPacket(ReflectionHelper.createActionbarPacket(ChatColor.translateAlternateColorCodes('&', message)), players);
+ }
+
+ /**
+ * @return The latest {@link MessagePart}
+ * @throws ArrayIndexOutOfBoundsException If {@code parts.size() <= 0}.
+ */
+ public MessagePart last() {
+ if (parts.size() <= 0) {
+ throw new ArrayIndexOutOfBoundsException("No MessageParts exist!");
+ }
+ return parts.get(parts.size() - 1);
+ }
+
+ /**
+ * Converts this {@link JSONMessage} instance to actual JSON
+ *
+ * @return The JSON representation of this {@link JSONMessage}
+ */
+ public JsonObject toJSON() {
+ JsonObject obj = new JsonObject();
+
+ obj.addProperty("text", "");
+
+ JsonArray array = new JsonArray();
+
+ parts.stream()
+ .map(MessagePart::toJSON)
+ .forEach(array::add);
+
+ obj.add("extra", array);
+
+ return obj;
+ }
+
+ /**
+ * Converts this {@link JSONMessage} object to a String representation of the JSON.
+ * This is an alias of {@code toJSON().toString()}.
+ */
+ @Override
+ public String toString() {
+ return toJSON().toString();
+ }
+
+ /**
+ * Converts this {@link JSONMessage} object to the legacy formatting system, which
+ * uses formatting codes (like &6, &l, &4, etc.)
+ *
+ * @return This {@link JSONMessage} instance {@link JSONMessage} in legacy format
+ */
+ public String toLegacy() {
+ StringBuilder output = new StringBuilder();
+
+ parts.stream()
+ .map(MessagePart::toLegacy)
+ .forEach(output::append);
+
+ return output.toString();
+ }
+
+ /**
+ * Sends this {@link JSONMessage} to all the players specified
+ *
+ * @param players The players you want to send this to
+ */
+ public void send(Player... players) {
+ if (ReflectionHelper.getStringVersion().equalsIgnoreCase("v1_16_R1")) {
+ ReflectionHelper.sendTextPacket(toString(), players);
+ return;
+ }
+
+ ReflectionHelper.sendPacket(ReflectionHelper.createTextPacket(toString()), players);
+ }
+
+ /**
+ * Sends this as a title to all the players specified
+ *
+ * @param fadeIn How many ticks to fade in
+ * @param stay How many ticks to stay
+ * @param fadeOut How many ticks to fade out
+ * @param players The players to send this to
+ */
+ public void title(int fadeIn, int stay, int fadeOut, Player... players) {
+ ReflectionHelper.sendPacket(ReflectionHelper.createTitleTimesPacket(fadeIn, stay, fadeOut), players);
+ ReflectionHelper.sendPacket(ReflectionHelper.createTitlePacket(toString()), players);
+ }
+
+ /**
+ * Sends this as a subtitle to all the players specified. Must be used after sending a {@link #title(int, int, int, Player...) title}.
+ *
+ * @param players The players to send this to
+ */
+ public void subtitle(Player... players) {
+ ReflectionHelper.sendPacket(ReflectionHelper.createSubtitlePacket(toString()), players);
+ }
+
+ /**
+ * Sends an action bar message
+ *
+ * @param players The players you want to send this to
+ */
+ public void actionbar(Player... players) {
+ actionbar(toLegacy(), players);
+ }
+
+ /**
+ * Sets the color of the current message part.
+ *
+ * @param color The color to set
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage color(ChatColor color) {
+ last().setColor(color);
+ return this;
+ }
+
+ /**
+ * Adds a style to the current message part.
+ *
+ * @param style The style to add
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage style(ChatColor style) {
+ last().addStyle(style);
+ return this;
+ }
+
+ /**
+ * Makes the text run a command.
+ *
+ * @param command The command to run
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage runCommand(String command) {
+ last().setOnClick(ClickEvent.runCommand(command));
+ return this;
+ }
+
+ /**
+ * Makes the text suggest a command.
+ *
+ * @param command The command to suggest
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage suggestCommand(String command) {
+ last().setOnClick(ClickEvent.suggestCommand(command));
+ return this;
+ }
+
+ /**
+ * Opens a URL.
+ *
+ * @param url The url to open
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage openURL(String url) {
+ last().setOnClick(ClickEvent.openURL(url));
+ return this;
+ }
+
+ /**
+ * Changes the page of a book. Using this in a non-book context is useless
+ * and will probably error.
+ *
+ * @param page The page to change to
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage changePage(int page) {
+ last().setOnClick(ClickEvent.changePage(page));
+ return this;
+ }
+
+ /**
+ * Shows text when you hover over it
+ *
+ * @param text The text to show
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage tooltip(String text) {
+ last().setOnHover(HoverEvent.showText(text));
+ return this;
+ }
+
+ /**
+ * Shows text when you hover over it
+ *
+ * @param message The text to show
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage tooltip(JSONMessage message) {
+ last().setOnHover(HoverEvent.showText(message));
+ return this;
+ }
+
+ /**
+ * Shows an achievement when you hover over it
+ *
+ * @param id The id of the achievement
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage achievement(String id) {
+ last().setOnHover(HoverEvent.showAchievement(id));
+ return this;
+ }
+
+ /**
+ * Adds another part to this {@link JSONMessage}
+ *
+ * @param text The text to start the next {@link MessagePart} with
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage then(String text) {
+ return then(new MessagePart(text));
+ }
+
+ /**
+ * Adds another part to this {@link JSONMessage}
+ *
+ * @param nextPart The next {@link MessagePart}
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage then(MessagePart nextPart) {
+ parts.add(nextPart);
+ return this;
+ }
+
+ /**
+ * Adds a horizontal bar to the message of the given length
+ *
+ * @param length The length of the horizontal bar
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage bar(int length) {
+ return then(Strings.repeat("-", length)).color(ChatColor.DARK_GRAY).style(ChatColor.STRIKETHROUGH);
+ }
+
+ /**
+ * Adds a horizontal bar to the message that's 53 characters long. This is
+ * the default width of the player's chat window.
+ *
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage bar() {
+ return bar(53);
+ }
+
+ /**
+ * Adds a blank line to the message
+ *
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage newline() {
+ return then("\n");
+ }
+
+ /**
+ * Sets the starting point to begin centering JSONMessages.
+ *
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage beginCenter() {
+ // Start with the NEXT message part.
+ centeringStartIndex = parts.size();
+ return this;
+ }
+
+ /**
+ * Ends the centering of the JSONMessage text.
+ *
+ * @return This {@link JSONMessage} instance
+ */
+ public JSONMessage endCenter() {
+ int current = centeringStartIndex;
+
+ while (current < parts.size()) {
+ Vector currentLine = new Vector<>();
+ int totalLineLength = 0;
+
+ for (; ; current++) {
+ MessagePart part = current < parts.size() ? parts.get(current) : null;
+ String raw = part == null ? null : ChatColor.stripColor(part.toLegacy());
+
+ if (current >= parts.size() || totalLineLength + raw.length() >= 53) {
+ int padding = Math.max(0, (53 - totalLineLength) / 2);
+ currentLine.firstElement().setText(Strings.repeat(" ", padding) + currentLine.firstElement().getText());
+ currentLine.lastElement().setText(currentLine.lastElement().getText() + "\n");
+ currentLine.clear();
+ break;
+ }
+
+ totalLineLength += raw.length();
+ currentLine.add(part);
+ }
+ }
+
+ MessagePart last = parts.get(parts.size() - 1);
+ last.setText(last.getText().substring(0, last.getText().length() - 1));
+
+ centeringStartIndex = -1;
+
+ return this;
+ }
+
+ ///////////////////////////
+ // BEGIN UTILITY CLASSES //
+ ///////////////////////////
+
+ /**
+ * Represents the JSON format that all click/hover events in JSON messages must follow.
+ *
+ *
+ * Reference
+ *
+ * @author Rayzr
+ */
+ public static class MessageEvent {
+
+ private String action;
+ private Object value;
+
+ public MessageEvent(String action, Object value) {
+ this.action = action;
+ this.value = value;
+ }
+
+ /**
+ * @return A {@link JsonObject} representing the properties of this {@link MessageEvent}
+ */
+ public JsonObject toJSON() {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("action", action);
+ if (value instanceof JsonElement) {
+ obj.add("value", (JsonElement) value);
+ } else {
+ obj.addProperty("value", value.toString());
+ }
+ return obj;
+ }
+
+ /**
+ * @return The action
+ */
+ public String getAction() {
+ return action;
+ }
+
+ /**
+ * @param action The action to set
+ */
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ /**
+ * @return The value
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * @param value The value to set
+ */
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ }
+
+ public static class ClickEvent {
+
+ /**
+ * Runs a command.
+ *
+ * @param command The command to run
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent runCommand(String command) {
+ return new MessageEvent("run_command", command);
+ }
+
+ /**
+ * Suggests a command by putting inserting it in chat.
+ *
+ * @param command The command to suggest
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent suggestCommand(String command) {
+ return new MessageEvent("suggest_command", command);
+ }
+
+ /**
+ * Requires web links to be enabled on the client.
+ *
+ * @param url The url to open
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent openURL(String url) {
+ return new MessageEvent("open_url", url);
+ }
+
+ /**
+ * Only used with written books.
+ *
+ * @param page The page to switch to
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent changePage(int page) {
+ return new MessageEvent("change_page", page);
+ }
+
+ }
+
+ public static class HoverEvent {
+
+ /**
+ * Shows text when you hover over it
+ *
+ * @param text The text to show
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent showText(String text) {
+ return new MessageEvent("show_text", text);
+ }
+
+ /**
+ * Shows text when you hover over it
+ *
+ * @param message The {@link JSONMessage} to show
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent showText(JSONMessage message) {
+ JsonArray arr = new JsonArray();
+ arr.add(new JsonPrimitive(""));
+ arr.add(message.toJSON());
+ return new MessageEvent("show_text", arr);
+ }
+
+ /**
+ * Shows an achievement when you hover over it
+ *
+ * @param id The id of the achievement
+ * @return The {@link MessageEvent}
+ */
+ public static MessageEvent showAchievement(String id) {
+ return new MessageEvent("show_achievement", id);
+ }
+
+ }
+
+ private static class ReflectionHelper {
+
+ private static final String version;
+ private static Class> craftPlayer;
+ private static Constructor> chatComponentText;
+ private static Class> packetPlayOutChat;
+ private static Class> packetPlayOutTitle;
+ private static Class> iChatBaseComponent;
+ private static Class> titleAction;
+ private static Field connection;
+ private static MethodHandle GET_HANDLE;
+ private static MethodHandle SEND_PACKET;
+ private static MethodHandle STRING_TO_CHAT;
+ private static Object enumActionTitle;
+ private static Object enumActionSubtitle;
+ private static Object enumChatMessage;
+ private static Object enumActionbarMessage;
+ private static boolean SETUP;
+ private static int MAJOR_VER = -1;
+
+ static {
+ String[] split = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
+ version = split[split.length - 1];
+
+ try {
+ SETUP = true;
+
+ MAJOR_VER = getVersion();
+
+ craftPlayer = getClass("{obc}.entity.CraftPlayer");
+ Method getHandle = craftPlayer.getMethod("getHandle");
+ connection = getHandle.getReturnType().getField("playerConnection");
+ Method sendPacket = connection.getType().getMethod("sendPacket", getClass("{nms}.Packet"));
+
+ chatComponentText = getClass("{nms}.ChatComponentText").getConstructor(String.class);
+
+ iChatBaseComponent = getClass("{nms}.IChatBaseComponent");
+
+ Method stringToChat;
+
+ if (MAJOR_VER < 8) {
+ stringToChat = getClass("{nms}.ChatSerializer").getMethod("a", String.class);
+ } else {
+ stringToChat = getClass("{nms}.IChatBaseComponent$ChatSerializer").getMethod("a", String.class);
+ }
+
+ GET_HANDLE = MethodHandles.lookup().unreflect(getHandle);
+ SEND_PACKET = MethodHandles.lookup().unreflect(sendPacket);
+ STRING_TO_CHAT = MethodHandles.lookup().unreflect(stringToChat);
+
+ packetPlayOutChat = getClass("{nms}.PacketPlayOutChat");
+ packetPlayOutTitle = getClass("{nms}.PacketPlayOutTitle");
+
+ titleAction = getClass("{nms}.PacketPlayOutTitle$EnumTitleAction");
+
+ enumActionTitle = titleAction.getField("TITLE").get(null);
+ enumActionSubtitle = titleAction.getField("SUBTITLE").get(null);
+
+ if (MAJOR_VER >= 12) {
+ Method getChatMessageType = getClass("{nms}.ChatMessageType").getMethod("a", byte.class);
+
+ enumChatMessage = getChatMessageType.invoke(null, (byte) 1);
+ enumActionbarMessage = getChatMessageType.invoke(null, (byte) 2);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ SETUP = false;
+ }
+
+ }
+
+ static void sendPacket(Object packet, Player... players) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ if (packet == null) {
+ return;
+ }
+
+ for (Player player : players) {
+ try {
+ SEND_PACKET.bindTo(connection.get(GET_HANDLE.bindTo(player).invoke())).invoke(packet);
+ } catch (Throwable e) {
+ System.err.println("Failed to send packet");
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ private static void setType(Object object, byte type) {
+ if (MAJOR_VER < 12) {
+ set("b", object, type);
+ return;
+ }
+
+ switch (type) {
+ case 1:
+ set("b", object, enumChatMessage);
+ break;
+ case 2:
+ set("b", object, enumActionbarMessage);
+ break;
+ default:
+ throw new IllegalArgumentException("type must be 1 or 2");
+ }
+ }
+
+ static Object createActionbarPacket(String message) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ Object packet = createTextPacket(message);
+ setType(packet, (byte) 2);
+ return packet;
+ }
+
+ static Object createTextPacket(String message) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ Object packet = packetPlayOutChat.newInstance();
+ set("a", packet, fromJson(message));
+ setType(packet, (byte) 1);
+ return packet;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ static void sendTextPacket(String message, Player... players) {
+ try {
+ for (Player player : players) {
+ Class chatTypeClass = getClass("{nms}.ChatMessageType");
+ Constructor> constructor = packetPlayOutChat.getConstructor(getClass("{nms}.IChatBaseComponent"), chatTypeClass, UUID.class);
+ Object packet = constructor.newInstance(fromJson(message), Enum.valueOf(chatTypeClass, "CHAT"), player.getUniqueId());
+
+ Object handler = player.getClass().getMethod("getHandle").invoke(player);
+ Object playerConnection = handler.getClass().getField("playerConnection").get(handler);
+ playerConnection.getClass().getMethod("sendPacket", getClass("{nms}.Packet")).invoke(playerConnection, packet);
+ }
+ } catch (IllegalArgumentException | NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException | InstantiationException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ static Object createTitlePacket(String message) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ return packetPlayOutTitle.getConstructor(titleAction, iChatBaseComponent).newInstance(enumActionTitle, fromJson(message));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ static Object createSubtitlePacket(String message) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ return packetPlayOutTitle.getConstructor(titleAction, iChatBaseComponent).newInstance(enumActionSubtitle, fromJson(message));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ static Object createTitleTimesPacket(int fadeIn, int stay, int fadeOut) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ return packetPlayOutTitle.getConstructor(int.class, int.class, int.class).newInstance(fadeIn, stay, fadeOut);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ /**
+ * Creates a ChatComponentText from plain text
+ *
+ * @param message The text to convert to a chat component
+ * @return The chat component
+ */
+ static Object componentText(String message) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ return chatComponentText.newInstance(message);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ /**
+ * Attempts to convert a String representing a JSON message into a usable object
+ *
+ * @param json The JSON to attempt to parse
+ * @return The object representing the text in JSON form, or null
if something went wrong converting the String to JSON data
+ */
+ static Object fromJson(String json) {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ if (!json.trim().startsWith("{")) {
+ return componentText(json);
+ }
+
+ try {
+ return STRING_TO_CHAT.invoke(json);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ }
+
+ /**
+ * Returns a class with the given package and name. This method replaces {nms}
with net.minecraft.server.[version]
and {obc}
with org.bukkit.craft.[version]
+ *
+ *
+ * Example:
+ *
+ *
+ * Class> entityPlayer = ReflectionHelper.getClass("{nms}.EntityPlayer");
+ *
+ *
+ * @param path The path to the {@link Class}
+ * @return The class
+ * @throws ClassNotFoundException If the class was not found
+ */
+ static Class> getClass(String path) throws ClassNotFoundException {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ return Class.forName(path.replace("{nms}", "net.minecraft.server." + version).replace("{obc}", "org.bukkit.craftbukkit." + version));
+ }
+
+ /**
+ * Sets a field with the given name on an object to the value specified
+ *
+ * @param field The name of the field to change
+ * @param obj The object to change the field of
+ * @param value The new value to set
+ */
+ static void set(String field, Object obj, Object value) {
+ try {
+ Field f = obj.getClass().getDeclaredField(field);
+ f.setAccessible(true);
+ f.set(obj, value);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String getStringVersion() {
+ return version;
+ }
+
+ static int getVersion() {
+ if (!SETUP) {
+ throw new IllegalStateException("ReflectionHelper is not set up!");
+ }
+ try {
+ return Integer.parseInt(version.split("_")[1]);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ return 10;
+ }
+
+ }
+
+ }
+
+ /**
+ * Defines a section of the message, and represents the format that all JSON messages must follow in Minecraft.
+ *
+ *
+ * Reference
+ *
+ * @author Rayzr
+ */
+ public static class MessagePart {
+
+ private final List styles = new ArrayList<>();
+ private MessageEvent onClick;
+ private MessageEvent onHover;
+ private ChatColor color;
+ private String text;
+
+ public MessagePart(String text) {
+ this.text = text == null ? "null" : text;
+ }
+
+ /**
+ * Converts this {@link MessagePart} into a {@link JsonObject}
+ *
+ * @return The Minecraft-compatible {@link JsonObject}
+ */
+ public JsonObject toJSON() {
+ Objects.requireNonNull(text);
+
+ JsonObject obj = new JsonObject();
+ obj.addProperty("text", text);
+
+ if (color != null) {
+ obj.addProperty("color", color.name().toLowerCase());
+ }
+
+ for (ChatColor style : styles) {
+ obj.addProperty(stylesToNames.get(style), true);
+ }
+
+ if (onClick != null) {
+ obj.add("clickEvent", onClick.toJSON());
+ }
+
+ if (onHover != null) {
+ obj.add("hoverEvent", onHover.toJSON());
+ }
+
+ return obj;
+
+ }
+
+ /**
+ * @return This {@link MessagePart} in legacy-style color/formatting codes
+ */
+ public String toLegacy() {
+ StringBuilder output = new StringBuilder();
+ if (color != null) {
+ output.append(color.toString());
+ }
+ styles.stream()
+ .map(ChatColor::toString)
+ .forEach(output::append);
+
+ return output.append(text).toString();
+ }
+
+ /**
+ * @return The click event bound
+ */
+ public MessageEvent getOnClick() {
+ return onClick;
+ }
+
+ /**
+ * @param onClick The new click event to bind
+ */
+ public void setOnClick(MessageEvent onClick) {
+ this.onClick = onClick;
+ }
+
+ /**
+ * @return The hover event bound
+ */
+ public MessageEvent getOnHover() {
+ return onHover;
+ }
+
+ /**
+ * @param onHover The new hover event to bind
+ */
+ public void setOnHover(MessageEvent onHover) {
+ this.onHover = onHover;
+ }
+
+ /**
+ * @return The color
+ */
+ public ChatColor getColor() {
+ return color;
+ }
+
+ /**
+ * @param color The color to set
+ */
+ public void setColor(ChatColor color) {
+ if (!color.isColor()) {
+ throw new IllegalArgumentException(color.name() + " is not a color!");
+ }
+ this.color = color;
+ }
+
+ /**
+ * @return The list of styles
+ */
+ public List getStyles() {
+ return styles;
+ }
+
+ /**
+ * @param style The new style to add
+ */
+ public void addStyle(ChatColor style) {
+ if (style == null) {
+ throw new IllegalArgumentException("Style cannot be null!");
+ }
+ if (!style.isFormat()) {
+ throw new IllegalArgumentException(color.name() + " is not a style!");
+ }
+ styles.add(style);
+ }
+
+ /**
+ * @return The raw text
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @param text The raw text to set
+ */
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/me/clip/placeholderapi/util/Msg.java b/src/main/java/me/clip/placeholderapi/util/Msg.java
index 73b87dd..3d526e4 100644
--- a/src/main/java/me/clip/placeholderapi/util/Msg.java
+++ b/src/main/java/me/clip/placeholderapi/util/Msg.java
@@ -25,18 +25,19 @@ import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Collectors;
-public class Msg {
+public final class Msg {
+ public static void msg(CommandSender s, String... msg) {
+ s.sendMessage(Arrays.stream(msg).filter(Objects::nonNull).map(Msg::color).collect(Collectors.joining("\n")));
+ }
- public static void msg(CommandSender s, String... msg) {
- Arrays.stream(msg).map(Msg::color).forEach(s::sendMessage);
- }
+ public static void broadcast(String... msg) {
+ Arrays.stream(msg).filter(Objects::nonNull).map(Msg::color).forEach(Bukkit::broadcastMessage);
+ }
- public static void broadcast(String... msg) {
- Arrays.stream(msg).map(Msg::color).forEach(Bukkit::broadcastMessage);
- }
-
- public static String color(String text) {
- return ChatColor.translateAlternateColorCodes('&', text);
- }
+ public static String color(String text) {
+ return ChatColor.translateAlternateColorCodes('&', text);
+ }
}
diff --git a/src/main/java/me/clip/placeholderapi/util/TimeFormat.java b/src/main/java/me/clip/placeholderapi/util/TimeFormat.java
index a338efc..a9c3190 100644
--- a/src/main/java/me/clip/placeholderapi/util/TimeFormat.java
+++ b/src/main/java/me/clip/placeholderapi/util/TimeFormat.java
@@ -21,8 +21,8 @@
package me.clip.placeholderapi.util;
public enum TimeFormat {
- DAYS,
- HOURS,
- MINUTES,
- SECONDS
+ DAYS,
+ HOURS,
+ MINUTES,
+ SECONDS
}
diff --git a/src/main/java/me/clip/placeholderapi/util/TimeUtil.java b/src/main/java/me/clip/placeholderapi/util/TimeUtil.java
index e076c67..b320ecf 100644
--- a/src/main/java/me/clip/placeholderapi/util/TimeUtil.java
+++ b/src/main/java/me/clip/placeholderapi/util/TimeUtil.java
@@ -20,156 +20,151 @@
*/
package me.clip.placeholderapi.util;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
public class TimeUtil {
- public static String getRemaining(int seconds, TimeFormat type) {
- if (seconds < 60) {
- switch (type) {
- case DAYS:
- case HOURS:
- case MINUTES:
- return "0";
- case SECONDS:
- return String.valueOf(seconds);
- }
+ public static String getRemaining(int seconds, TimeFormat type) {
+ if (seconds < 60) {
+ switch (type) {
+ case DAYS:
+ case HOURS:
+ case MINUTES:
+ return "0";
+ case SECONDS:
+ return String.valueOf(seconds);
+ }
- return String.valueOf(seconds);
+ return String.valueOf(seconds);
+ }
+
+ int minutes = seconds / 60;
+ int s = 60 * minutes;
+ int secondsLeft = seconds - s;
+
+ if (minutes < 60) {
+ switch (type) {
+ case DAYS:
+ case HOURS:
+ return "0";
+ case MINUTES:
+ return String.valueOf(minutes);
+ case SECONDS:
+ return String.valueOf(secondsLeft);
+ }
+
+ return String.valueOf(seconds);
+ }
+
+ if (minutes < 1440) {
+ int hours = minutes / 60;
+ int inMins = 60 * hours;
+ int leftOver = minutes - inMins;
+
+ switch (type) {
+ case DAYS:
+ return "0";
+ case HOURS:
+ return String.valueOf(hours);
+ case MINUTES:
+ return String.valueOf(leftOver);
+ case SECONDS:
+ return String.valueOf(secondsLeft);
+ }
+
+ return String.valueOf(seconds);
+ }
+
+ int days = minutes / 1440;
+ int inMins = 1440 * days;
+ int leftOver = minutes - inMins;
+
+ if (leftOver < 60) {
+ switch (type) {
+ case DAYS:
+ return String.valueOf(days);
+ case HOURS:
+ return String.valueOf(0);
+ case MINUTES:
+ return String.valueOf(leftOver);
+ case SECONDS:
+ return String.valueOf(secondsLeft);
+ }
+
+ return String.valueOf(seconds);
+
+ } else {
+ int hours = leftOver / 60;
+ int hoursInMins = 60 * hours;
+ int minsLeft = leftOver - hoursInMins;
+
+ switch (type) {
+ case DAYS:
+ return String.valueOf(days);
+ case HOURS:
+ return String.valueOf(hours);
+ case MINUTES:
+ return String.valueOf(minsLeft);
+ case SECONDS:
+ return String.valueOf(secondsLeft);
+ }
+
+ return String.valueOf(seconds);
+ }
}
- int minutes = seconds / 60;
- int s = 60 * minutes;
- int secondsLeft = seconds - s;
-
- if (minutes < 60) {
- switch (type) {
- case DAYS:
- case HOURS:
- return "0";
- case MINUTES:
- return String.valueOf(minutes);
- case SECONDS:
- return String.valueOf(secondsLeft);
- }
-
- return String.valueOf(seconds);
+ public static String getTime(int seconds) {
+ return getTime(Duration.ofSeconds(seconds));
}
- if (minutes < 1440) {
- int hours = minutes / 60;
- int inMins = 60 * hours;
- int leftOver = minutes - inMins;
+ /**
+ * Format the given value with s, m, h and d (seconds, minutes, hours and days)
+ *
+ * @param duration {@link Duration} (eg, Duration.of(20, {@link ChronoUnit#SECONDS}) for 20 seconds)
+ * @return formatted time
+ */
+ public static String getTime(final Duration duration) {
+ final StringBuilder builder = new StringBuilder();
- switch (type) {
- case DAYS:
- return "0";
- case HOURS:
- return String.valueOf(hours);
- case MINUTES:
- return String.valueOf(leftOver);
- case SECONDS:
- return String.valueOf(secondsLeft);
- }
+ long seconds = duration.getSeconds();
+ long minutes = seconds / 60;
+ long hours = minutes / 60;
+ long days = hours / 24;
- return String.valueOf(seconds);
+ seconds %= 60;
+ minutes %= 60;
+ hours %= 60;
+ days %= 24;
+
+ if (seconds > 0) {
+ builder.insert(0, seconds + "s");
+ }
+
+ if (minutes > 0) {
+ if (builder.length() > 0) {
+ builder.insert(0, ' ');
+ }
+
+ builder.insert(0, minutes + "m");
+ }
+
+ if (hours > 0) {
+ if (builder.length() > 0) {
+ builder.insert(0, ' ');
+ }
+
+ builder.insert(0, hours + "h");
+ }
+
+ if (days > 0) {
+ if (builder.length() > 0) {
+ builder.insert(0, ' ');
+ }
+
+ builder.insert(0, days + "d");
+ }
+
+ return builder.toString();
}
-
- int days = minutes / 1440;
- int inMins = 1440 * days;
- int leftOver = minutes - inMins;
-
- if (leftOver < 60) {
- switch (type) {
- case DAYS:
- return String.valueOf(days);
- case HOURS:
- return String.valueOf(0);
- case MINUTES:
- return String.valueOf(leftOver);
- case SECONDS:
- return String.valueOf(secondsLeft);
- }
-
- return String.valueOf(seconds);
-
- } else {
- int hours = leftOver / 60;
- int hoursInMins = 60 * hours;
- int minsLeft = leftOver - hoursInMins;
-
- switch (type) {
- case DAYS:
- return String.valueOf(days);
- case HOURS:
- return String.valueOf(hours);
- case MINUTES:
- return String.valueOf(minsLeft);
- case SECONDS:
- return String.valueOf(secondsLeft);
- }
-
- return String.valueOf(seconds);
- }
- }
-
- public static String getTime(int seconds) {
-
- if (seconds < 60) {
- return seconds + "s";
- }
-
- int minutes = seconds / 60;
- int s = 60 * minutes;
- int secondsLeft = seconds - s;
-
- if (minutes < 60) {
- if (secondsLeft > 0) {
- return minutes + "m " + secondsLeft + "s";
- } else {
- return minutes + "m";
- }
- }
-
- if (minutes < 1440) {
- String time;
- int hours = minutes / 60;
- time = hours + "h";
- int inMins = 60 * hours;
- int leftOver = minutes - inMins;
-
- if (leftOver >= 1) {
- time = time + " " + leftOver + "m";
- }
-
- if (secondsLeft > 0) {
- time = time + " " + secondsLeft + "s";
- }
-
- return time;
- }
-
- String time;
- int days = minutes / 1440;
- time = days + "d";
- int inMins = 1440 * days;
- int leftOver = minutes - inMins;
-
- if (leftOver >= 1) {
- if (leftOver < 60) {
- time = time + " " + leftOver + "m";
- } else {
- int hours = leftOver / 60;
- time = time + " " + hours + "h";
-
- int hoursInMins = 60 * hours;
- int minsLeft = leftOver - hoursInMins;
- time = time + " " + minsLeft + "m";
- }
- }
-
- if (secondsLeft > 0) {
- time = time + " " + secondsLeft + "s";
- }
-
- return time;
- }
}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 3394896..d4031ef 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,7 +1,7 @@
name: ${project.name}
main: me.clip.placeholderapi.PlaceholderAPIPlugin
version: ${project.version}
-api-version: 1.13
+api-version: '1.13'
authors: [extended_clip, Glare]
description: ${project.description}
permissions: