package net.glowstone.command;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.entity.Player;
import org.bukkit.title.Title;
import org.bukkit.title.TitleOptions;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import java.util.Arrays;
public class TitleCommand extends BukkitCommand {
public TitleCommand() {
super("title");
this.description = "Sends a title to the specified player(s)";
this.usageMessage = "/title <player> <title|subtitle|clear|reset|times> ...";
this.setAliases(Arrays.<String>asList());
this.setPermission("glowstone.command.title");
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if (!testPermission(sender)) return true;
if (args.length < 2) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
Player player = Bukkit.getPlayerExact(args[0]);
if (player == null || (sender instanceof Player && !((Player) sender).canSee(player))) {
sender.sendMessage("There's no player by that name online.");
return false;
}
if (args[1].equalsIgnoreCase("clear")) {
player.clearTitle();
sender.sendMessage("Cleared " + player.getName() + "'s title");
} else if (args[1].equalsIgnoreCase("reset")) {
player.resetTitle();
sender.sendMessage("Reset " + player.getName() + "'s title");
} else if (args[1].equalsIgnoreCase("times")) {
if (args.length < 5) {
sender.sendMessage(ChatColor.RED + "Usage: /title <player> times <fadeIn> <stay> <fadeOut>");
return false;
}
TitleOptions options = player.getTitleOptions();
int in = tryParseInt(sender, args[2], 0);
int stay = tryParseInt(sender, args[3], in);
int out = tryParseInt(sender, args[4], stay);
if (out == -1) {
return false;
}
options.setFadeInTime(in);
options.setFadeOutTime(out);
options.setVisibleTime(stay);
player.setTitleOptions(options);
sender.sendMessage("Updated " + player.getName() + "'s title times");
} else if (args[1].equalsIgnoreCase("title") || args[1].equalsIgnoreCase("subtitle")) {
if (args.length < 3) {
sender.sendMessage(ChatColor.RED + "Usage: /title <player> " + args[1] + " <raw json>");
return false;
}
StringBuilder message = new StringBuilder();
for (int i = 2; i < args.length; i++) {
if (i > 2) message.append(" ");
message.append(args[i]);
}
String raw = message.toString().trim();
if (!validJson(raw)) {
sender.sendMessage(ChatColor.RED + "Invalid JSON: Could not parse, invalid format?");
return false;
}
String component = raw;
Object parsed = JSONValue.parse(raw);
if (parsed != null && parsed instanceof JSONObject)
component = convertJson((JSONObject) parsed);
Title currentTitle = player.getTitle();
if (args[1].equalsIgnoreCase("title")) {
currentTitle.setHeading(component);
} else {
currentTitle.setSubtitle(component);
}
player.setTitle(currentTitle, args[1].equalsIgnoreCase("title"));
sender.sendMessage("Updated " + player.getName() + "'s title");
} else {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}
return true;
}
private int tryParseInt(CommandSender sender, String number, int lastParse) {
if (lastParse == -1) {
return -1;
}
int n = -1;
try {
n = Integer.parseInt(number);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "'" + number + "' is not a number");
}
return n;
}
// TODO: Replace with a proper JSON chat component validator
private boolean validJson(String raw) {
Object object = JSONValue.parse(raw);
if (object == null) {
// Could not parse JSON: Check to see if it's at least a single word
// Rule set:
// 1. Cannot contain a space (or else the client fails)
// 2. Must not look like JSON (first character check is sufficient)
return !raw.contains(" ") && raw.charAt(0) != '{' && raw.charAt(0) != '[';
}
if (!(object instanceof JSONObject))
return false; // Not a valid JSON object
JSONObject json = (JSONObject) object;
// Needs a text component at least
if (!json.containsKey("text") || !(json.get("text") instanceof String))
return false;
// If 'extra' is present, some extra handling needs to be done
if (json.containsKey("extra")) {
Object extraObj = json.get("extra");
// Check to make sure it's a valid component
if (!(extraObj instanceof JSONArray)) return false;
// Check all components in 'extra'
JSONArray extra = (JSONArray) extraObj;
if (extra.isEmpty()) return false; // No components
for (Object o : extra) {
if (!(o instanceof JSONObject)) return false;
// Check to make sure there's a text component
JSONObject e = (JSONObject) o;
if (!e.containsKey("text") || !(e.get("text") instanceof String))
return false;
}
}
// If we made it this far then it has a pretty good chance at being valid
return true;
}
/**
* Converts a valid JSON chat component to a basic colored string. This does not parse
* components like hover or click events. This returns null on parse failure.
*
* @param json the json chat component
*
* @return the colored string, or null
*/
// TODO: Replace with proper chat components when possible
public static String convertJson(JSONObject json) {
if (json == null || !json.containsKey("text") && !(json.get("text") instanceof String))
return null; // We can't even parse this
ChatColor color = ChatColor.WHITE;
if (json.containsKey("color")) {
if (!(json.get("color") instanceof String)) return null;
color = toColor((String) json.get("color"));
}
if (color == null) return null; // Invalid color
String text = color + (String) json.get("text");
// Check for "extra"
if (json.containsKey("extra")) {
Object extraObj = json.get("extra");
// Check to make sure it's a valid component
if (!(extraObj instanceof JSONArray)) return null;
// Check all components in 'extra'
JSONArray extra = (JSONArray) extraObj;
if (extra.isEmpty()) return null;
for (Object o : extra) {
if (!(o instanceof JSONObject)) return null;
JSONObject e = (JSONObject) o;
// Attempt to parse the component
String temp = convertJson(e);
if (temp == null) return null; // Could not parse 'extra'
// It's valid, append it
text += temp;
}
}
return text;
}
private static ChatColor toColor(String name) {
if (name.equals("obfuscated"))
return ChatColor.MAGIC;
// Loop to avoid exceptions, we'll just return null if it can't be parsed
for (ChatColor color : ChatColor.values()) {
if (color == ChatColor.MAGIC) continue; // This isn't a valid value for color anyways
if (color.name().equals(name.toUpperCase()))
return color;
}
return null;
}
}