package com.github.shynixn.petblocks.bukkit.lib;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
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.Collection;
import java.util.List;
import java.util.logging.Level;
/**
* Copyright 2017 Shynixn
*
* Do not remove this header!
*
* Version 1.0
*
* MIT License
*
* Copyright (c) 2017
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
public class ChatBuilder {
private final List components = new ArrayList<>();
/**
* Adds a text
*
* @param text text
* @return instance
*/
public ChatBuilder text(String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.components.add(text);
return this;
}
/**
* Creates a component for complex editing from the text and returns it for editing
*
* @param text text
* @return created component
*/
public Component component(String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
final Component component = new Component(this, text);
this.components.add(component);
return component;
}
/**
* Sets the text bold
*
* @return instance
*/
public ChatBuilder bold() {
this.components.add(ChatColor.BOLD);
return this;
}
/**
* Sets the text italic
*
* @return instance
*/
public ChatBuilder italic() {
this.components.add(ChatColor.ITALIC);
return this;
}
/**
* Sets the text underlined
*
* @return instance
*/
public ChatBuilder underline() {
this.components.add(ChatColor.UNDERLINE);
return this;
}
/**
* Sets the text strikeThrough
*
* @return instance
*/
public ChatBuilder strikeThrough() {
this.components.add(ChatColor.STRIKETHROUGH);
return this;
}
/**
* Sets the current color
*
* @param chatColor color
* @return instance
*/
public ChatBuilder color(ChatColor chatColor) {
if (chatColor == null)
throw new IllegalArgumentException("ChatColor cannot be null");
this.components.add(chatColor);
return this;
}
/**
* Resets the color
*
* @return instance
*/
public ChatBuilder reset() {
this.components.add(ChatColor.RESET);
return this;
}
/**
* Sends the built message to the given players
*
* @param players players
*/
public void sendMessage(Collection players) {
if (players == null)
throw new IllegalArgumentException("Players cannot be null");
this.sendMessage(players.toArray(new Player[players.size()]));
}
/**
* Sends the built message to the given players
*
* @param players players
*/
public void sendMessage(Player... players) {
if (players == null)
throw new IllegalArgumentException("Players cannot be null");
final StringBuilder finalMessage = new StringBuilder();
final StringBuilder cache = new StringBuilder();
finalMessage.append("{\"text\": \"\"");
finalMessage.append(", \"extra\" : [");
boolean firstExtra = false;
for (final Object component : this.components) {
if (!(component instanceof ChatColor) && firstExtra) {
finalMessage.append(", ");
}
if (component instanceof ChatColor) {
cache.append(component);
} else if (component instanceof String) {
finalMessage.append("{\"text\": \"");
finalMessage.append(ChatColor.translateAlternateColorCodes('&', cache.toString() + component));
finalMessage.append("\"}");
cache.setLength(0);
firstExtra = true;
} else {
finalMessage.append(component);
firstExtra = true;
}
}
finalMessage.append("]}");
try {
final Class> clazz;
if (Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3].equals("v1_8_R1")) {
clazz = findClass("net.minecraft.server.VERSION.ChatSerializer");
} else {
clazz = findClass("net.minecraft.server.VERSION.IChatBaseComponent$ChatSerializer");
}
final Class> packetClazz = findClass("net.minecraft.server.VERSION.PacketPlayOutChat");
final Class> chatBaseComponentClazz = findClass("net.minecraft.server.VERSION.IChatBaseComponent");
final Object chatComponent = invokeMethod(null, clazz, "a", new Class[]{String.class}, new Object[]{finalMessage.toString()});
final Object packet;
if (Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3].equals("v1_12_R1")) {
final Class> chatEnumMessage = findClass("net.minecraft.server.VERSION.ChatMessageType");
packet = invokeConstructor(packetClazz, new Class[]{chatBaseComponentClazz, chatEnumMessage}, new Object[]{chatComponent, chatEnumMessage.getEnumConstants()[0]});
} else {
packet = invokeConstructor(packetClazz, new Class[]{chatBaseComponentClazz, byte.class}, new Object[]{chatComponent, (byte) 0});
}
for (final Player player : players) {
sendPacket(player, packet);
}
} catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException | InstantiationException | NoSuchFieldException e) {
Bukkit.getLogger().log(Level.WARNING, "Failed to send packet.", e);
}
}
/**
* Sends a packet to the client player
*
* @param player player
* @param packet packet
* @throws ClassNotFoundException exception
* @throws IllegalAccessException exception
* @throws NoSuchMethodException exception
* @throws InvocationTargetException exception
* @throws NoSuchFieldException exception
*/
private static void sendPacket(Player player, Object packet) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
final Object craftPlayer = findClass("org.bukkit.craftbukkit.VERSION.entity.CraftPlayer").cast(player);
final Object entityPlayer = invokeMethod(craftPlayer, craftPlayer.getClass(), "getHandle", new Class[]{}, new Object[]{});
final Field field = entityPlayer.getClass().getDeclaredField("playerConnection");
field.setAccessible(true);
final Object connection = field.get(entityPlayer);
invokeMethod(connection, connection.getClass(), "sendPacket", new Class[]{packet.getClass().getInterfaces()[0]}, new Object[]{packet});
}
/**
* Invokes a constructor by the given parameters
*
* @param clazz clazz
* @param paramTypes paramTypes
* @param params params
* @return instance
* @throws NoSuchMethodException exception
* @throws IllegalAccessException exception
* @throws InvocationTargetException exception
* @throws InstantiationException exception
*/
private static Object invokeConstructor(Class> clazz, Class[] paramTypes, Object[] params) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
final Constructor constructor = clazz.getDeclaredConstructor(paramTypes);
constructor.setAccessible(true);
return constructor.newInstance(params);
}
/**
* Invokes a method by the given parameters
*
* @param instance instance
* @param clazz clazz
* @param name name
* @param paramTypes paramTypes
* @param params params
* @return returnedObject
* @throws InvocationTargetException exception
* @throws IllegalAccessException exception
* @throws NoSuchMethodException exception
*/
private static Object invokeMethod(Object instance, Class> clazz, String name, Class[] paramTypes, Object[] params) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
final Method method = clazz.getDeclaredMethod(name, paramTypes);
method.setAccessible(true);
return method.invoke(instance, params);
}
/**
* Finds a class regarding of the server Version
*
* @param name name
* @return clazz
* @throws ClassNotFoundException exception
*/
private static Class> findClass(String name) throws ClassNotFoundException {
return Class.forName(name.replace("VERSION", Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3]));
}
/**
* Types of ClickAction
*/
public enum ClickAction {
OPEN_URL,
OPEN_FILE,
RUN_COMMAND,
SUGGEST_COMMAND,
CHANGE_PAGE,
}
public static class Component {
private final StringBuilder text = new StringBuilder();
private ChatColor color;
private boolean bold;
private boolean italic;
private boolean underlined;
private boolean strikethrough;
private final ChatBuilder builder;
private ClickAction clickAction;
private String clickActionData;
private Component hoverActionData;
private final Component parentComponent;
/**
* Initializes a new component with the given builder and text
*
* @param builder builder
* @param text text
*/
Component(ChatBuilder builder, String text) {
this(builder, text, null);
}
/**
* Initializes a new component with the given builder text and parentComponent
*
* @param builder builder
* @param text text
* @param component parent
*/
Component(ChatBuilder builder, String text, Component component) {
super();
if (builder == null)
throw new IllegalArgumentException("Builder cannot be null!");
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.builder = builder;
this.text.append(text);
this.parentComponent = component;
}
/**
* Returns the parent component. Returns null if there is no parent
*
* @return componend
*/
public Component getParentComponent() {
return this.parentComponent;
}
/**
* Returns the builder of the component
*
* @return builder
*/
public ChatBuilder builder() {
return this.builder;
}
/**
* Sets the text of the component
*
* @param text text
* @return instance
*/
public Component setText(String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.text.setLength(0);
this.text.append(text);
return this;
}
/**
* Sets the click Action of the component
*
* @param action action
* @param text text
* @return instance
*/
public Component setClickAction(ClickAction action, String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.clickAction = action;
this.clickActionData = text;
return this;
}
/**
* Sets the hover text of the component and returns the component for the hover-text
*
* @param text text
* @return childComponent
*/
public Component setHoverText(String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.hoverActionData = new Component(this.builder, text, this);
return this.hoverActionData;
}
/**
* Returns the text
*
* @return text
*/
public String getText() {
return ChatColor.translateAlternateColorCodes('&', this.text.toString());
}
/**
* Appends a text to the component
*
* @param text text
* @return instance
*/
public Component appendText(String text) {
if (text == null)
throw new IllegalArgumentException("Text cannot be null!");
this.text.append(text);
return this;
}
/**
* Sets the component color
*
* @param color color
* @return instance
*/
public Component setColor(ChatColor color) {
this.color = color;
return this;
}
/**
* Returns if the component is bold
*
* @return bold
*/
public boolean isBold() {
return this.bold;
}
/**
* Sets the component bold
*
* @param bold bold
* @return isBold
*/
public Component setBold(boolean bold) {
this.bold = bold;
return this;
}
/**
* Returns if the component is italic
*
* @return isItalic
*/
public boolean isItalic() {
return this.italic;
}
/**
* Sets the component italic
*
* @param italic italic
* @return instance
*/
public Component setItalic(boolean italic) {
this.italic = italic;
return this;
}
/**
* Returns if the component isUnderlined
*
* @return isUnderLined
*/
public boolean isUnderlined() {
return this.underlined;
}
/**
* Sets the component underLined
*
* @param underlined underLines
* @return instance
*/
public Component setUnderlined(boolean underlined) {
this.underlined = underlined;
return this;
}
/**
* Returns if the component isStrikeThrough
*
* @return isStrikeTrough
*/
public boolean isStrikethrough() {
return this.strikethrough;
}
/**
* Sets the component strikeThrough
*
* @param strikethrough strikeThrough
* @return instance
*/
public Component setStrikethrough(boolean strikethrough) {
this.strikethrough = strikethrough;
return this;
}
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("{ \"text\": \"");
builder.append(ChatColor.translateAlternateColorCodes('&', this.text.toString()));
builder.append('"');
if (this.color != null) {
builder.append(", \"color\": \"");
builder.append(this.color.name().toLowerCase());
builder.append('"');
}
if (this.bold) {
builder.append(", \"bold\": \"");
builder.append(this.bold);
builder.append('"');
}
if (this.italic) {
builder.append(", \"italic\": \"");
builder.append(this.italic);
builder.append('"');
}
if (this.underlined) {
builder.append(", \"underlined\": \"");
builder.append(this.underlined);
builder.append('"');
}
if (this.strikethrough) {
builder.append(", \"strikethrough\": \"");
builder.append(this.strikethrough);
builder.append('"');
}
if (this.clickAction != null) {
builder.append(", \"clickEvent\": {\"action\": \"");
builder.append(this.clickAction.name().toLowerCase());
builder.append("\" , \"value\" : \"");
builder.append(this.clickActionData);
builder.append("\"}");
}
if (this.hoverActionData != null) {
builder.append(", \"hoverEvent\": {\"action\": \"");
builder.append("show_text");
builder.append("\" , \"value\" : ");
builder.append(this.hoverActionData.toString());
builder.append('}');
}
builder.append('}');
return builder.toString();
}
}
}