blob: e7d0e258afe16a348b18636f097466689dad4e5d [file] [log] [blame] [raw]
package net.glowstone.command.minecraft;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.i18n.LocalizedStringImpl;
import net.glowstone.net.message.play.game.TitleMessage;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NonNls;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
public class TitleCommand extends GlowVanillaCommand {
/**
* Creates the instance for this command.
*/
public TitleCommand() {
super("title");
setPermission("minecraft.command.title"); // NON-NLS
}
private static ChatColor toColor(String name) {
if (name.equals("obfuscated")) { // NON-NLS
return ChatColor.MAGIC;
}
if (name.equals("underlined")) { // NON-NLS
return ChatColor.UNDERLINE;
}
// 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().equalsIgnoreCase(name.toUpperCase())) {
return color;
}
}
return null;
}
/**
* 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
*/
public String convertJson(@NonNls Map<String, Object> json) {
if (json == null || !json.containsKey("text") && !(json.get("text") instanceof String)) {
return null; // We can't even parse this
}
ChatColor color = ChatColor.WHITE;
List<ChatColor> style = new ArrayList<>();
for (Object key : json.keySet()) {
if (!(key instanceof String)) {
continue;
}
String keyString = (String) key;
if (keyString.equalsIgnoreCase("color")) { // NON-NLS
if (!(json.get("color") instanceof String)) {
return null;
}
color = toColor((String) json.get(keyString));
} else if (!keyString.equalsIgnoreCase("text")) { // NON-NLS
if (toColor(keyString) == null) {
return null;
}
style.add(toColor(keyString));
}
}
style.add(color);
String text = (String) json.get("text"); // NON-NLS
for (ChatColor c : style) {
text = c + text;
}
return text;
}
// CAUTION: Most usage messages in this method can't be replaced with sendUsageMessage, because
// they're subcommand-specific.
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args,
CommandMessages commandMessages) {
if (!testPermission(sender, commandMessages.getPermissionMessage())) {
return true;
}
if (args.length < 2) {
sendUsageMessage(sender, commandMessages);
return false;
}
Player player = Bukkit.getPlayerExact(args[0]);
if (player == null || sender instanceof Player && !((Player) sender).canSee(player)) {
commandMessages.getGeneric(GenericMessage.NO_SUCH_PLAYER).send(sender, args[0]);
return false;
}
@NonNls String action = args[1];
if (action.equalsIgnoreCase("clear")) {
((GlowPlayer) player).clearTitle();
new LocalizedStringImpl("title.done.clear", commandMessages.getResourceBundle())
.send(sender, player.getName());
} else if (action.equalsIgnoreCase("reset")) {
player.resetTitle();
new LocalizedStringImpl("title.done.reset", commandMessages.getResourceBundle())
.send(sender, player.getName());
} else if (action.equalsIgnoreCase("title")) {
if (args.length < 3) {
sendUsageMessage(sender, commandMessages);
sender.sendMessage(
ChatColor.RED + "Usage: /title <player> " + action + " <raw json>");
return false;
}
StringBuilder message = new StringBuilder();
for (int i = 2; i < args.length; i++) {
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;
Map<String, Object> parsed = getJson(raw);
if (parsed != null) {
component = convertJson(parsed);
}
((GlowPlayer) player).updateTitle(TitleMessage.Action.TITLE, component);
((GlowPlayer) player).sendTitle();
sender.sendMessage("Updated " + player.getName() + "'s title");
} else if (action.equalsIgnoreCase("subtitle")) {
if (args.length < 3) {
sender.sendMessage(
ChatColor.RED + "Usage: /title <player> " + action + " <raw json>");
return false;
}
StringBuilder message = new StringBuilder();
for (int i = 2; i < args.length; i++) {
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 instanceof JSONObject) {
component = convertJson((JSONObject) parsed);
}
((GlowPlayer) player).updateTitle(TitleMessage.Action.SUBTITLE, component);
sender.sendMessage("Updated " + player.getName() + "'s subtitle");
} else if (action.equalsIgnoreCase("times")) {
if (args.length != 5) {
sender.sendMessage(ChatColor.RED + "Usage: /title <player> " + action
+ " <fade in> <stay time> <fade out>");
return false;
}
if (!tryParseInt(args[2])) {
sender.sendMessage(ChatColor.RED + "'" + args[2] + "' is not a number");
return false;
}
if (!tryParseInt(args[3])) {
sender.sendMessage(ChatColor.RED + "'" + args[3] + "' is not a number");
return false;
}
if (!tryParseInt(args[4])) {
sender.sendMessage(ChatColor.RED + "'" + args[4] + "' is not a number");
return false;
}
((GlowPlayer) player)
.updateTitle(TitleMessage.Action.TIMES, toInt(args[2]), toInt(args[3]),
toInt(args[4]));
sender.sendMessage("Updated " + player.getName() + "'s times");
} else {
sendUsageMessage(sender, commandMessages);
return false;
}
return true;
}
private boolean tryParseInt(String number) {
try {
Integer.parseInt(number);
} catch (NumberFormatException | NullPointerException e) {
return false;
}
// only got here if we didn't return false
return true;
}
private int toInt(String number) {
return Integer.parseInt(number.trim());
}
private boolean validJson(String raw) {
Map<String, Object> object = getJson(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) != '[';
}
Map<String, Object> json = object;
// Run through all of the keys to see if they are valid keys,
// and have valid values
for (Map.Entry<String, Object> entry : json.entrySet()) {
Object value = entry.getValue();
if (entry.getKey() == null) {
return false; // The key is empty, meaning that it is not valid
}
@NonNls String keyString = entry.getKey();
switch (keyString) {
case "text":
if (!(value instanceof String)) {
return false; // The value is not a valid type
}
break;
case "color":
if (!(value instanceof String)) {
return false; // The value is not a valid type
}
break;
case "bold":
if (!(value instanceof Boolean)) {
return false; // The value is not a valid type
}
break;
case "italic":
if (!(value instanceof Boolean)) {
return false; // The value is not a valid type
}
break;
case "underlined":
if (!(value instanceof Boolean)) {
return false; // The value is not a valid type
}
break;
case "strikethrough":
if (!(value instanceof Boolean)) {
return false; // The value is not a valid type
}
break;
case "obfuscated":
if (!(value instanceof Boolean)) {
return false; // The value is not a valid type
}
break;
default:
// The key is not in the list of valid keys,
// meaning that it is not a valid key
return false;
}
}
// If we made it this far then it has a pretty good chance at being valid
return true;
}
private Map<String, Object> getJson(String raw) {
Gson gson = new Gson();
try {
Map<String, Object> map = gson.fromJson(raw, new TypeToken<Map<String, Object>>() {
}.getType());
return map;
} catch (JsonSyntaxException e) {
// Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e);
return null;
}
}
}