diff --git a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java index 41eb5531c..81de33064 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java @@ -3,6 +3,7 @@ package com.earth2me.essentials; import com.earth2me.essentials.commands.IEssentialsCommand; import com.earth2me.essentials.signs.EssentialsSign; import com.earth2me.essentials.textreader.IText; +import net.essentialsx.api.v2.ChatType; import net.kyori.adventure.text.minimessage.tag.Tag; import org.bukkit.Material; import org.bukkit.event.EventPriority; @@ -37,6 +38,8 @@ public interface ISettings extends IConf { String getChatFormat(String group); + String getChatFormat(String group, ChatType chatType); + String getWorldAlias(String world); int getChatRadius(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index d57e5d913..908e2ca11 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -14,6 +14,7 @@ import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.LocationUtil; import com.earth2me.essentials.utils.NumberUtil; import net.ess3.api.IEssentials; +import net.essentialsx.api.v2.ChatType; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.tag.Tag; @@ -35,6 +36,7 @@ import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -45,6 +47,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -59,7 +62,7 @@ public class Settings implements net.ess3.api.ISettings { private final transient EssentialsConfiguration config; private final transient IEssentials ess; private final transient AtomicInteger reloadCount = new AtomicInteger(0); - private final Map chatFormats = Collections.synchronizedMap(new HashMap<>()); + private final ChatFormats chatFormats = new ChatFormats(); private int chatRadius = 0; // #easteregg private char chatShout = '!'; @@ -199,7 +202,7 @@ public class Settings implements net.ess3.api.ISettings { final Set homeList = getMultipleHomes(); if (homeList != null) { for (final String set : homeList) { - if (user.isAuthorized("essentials.sethome.multiple." + set) && (limit < getHomeLimit(set))) { + if (user.isAuthorized("essentials.sethome.multiple." + set) && limit < getHomeLimit(set)) { limit = getHomeLimit(set); } } @@ -590,32 +593,132 @@ public class Settings implements net.ess3.api.ISettings { @Override public String getChatFormat(final String group) { - String mFormat = chatFormats.get(group); - if (mFormat == null) { - mFormat = config.getString("chat.group-formats." + (group == null ? "Default" : group), config.getString("chat.format", "&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}")); - mFormat = FormatUtil.replaceFormat(mFormat); - mFormat = mFormat.replace("{DISPLAYNAME}", "%1$s"); - mFormat = mFormat.replace("{MESSAGE}", "%2$s"); - mFormat = mFormat.replace("{GROUP}", "{0}"); - mFormat = mFormat.replace("{WORLD}", "{1}"); - mFormat = mFormat.replace("{WORLDNAME}", "{1}"); - mFormat = mFormat.replace("{SHORTWORLDNAME}", "{2}"); - mFormat = mFormat.replace("{TEAMPREFIX}", "{3}"); - mFormat = mFormat.replace("{TEAMSUFFIX}", "{4}"); - mFormat = mFormat.replace("{TEAMNAME}", "{5}"); - mFormat = mFormat.replace("{PREFIX}", "{6}"); - mFormat = mFormat.replace("{SUFFIX}", "{7}"); - mFormat = mFormat.replace("{USERNAME}", "{8}"); - mFormat = mFormat.replace("{NICKNAME}", "{9}"); - mFormat = "§r".concat(mFormat); - chatFormats.put(group, mFormat); - } + return getChatFormat(group, null); + } + + @Override + public String getChatFormat(final String group, final ChatType chatType) { + final String mFormat = chatFormats.getFormat(group, chatType, new ChatFormatConfigSupplier(group, chatType)); if (isDebug()) { ess.getLogger().info(String.format("Found format '%s' for group '%s'", mFormat, group)); } return mFormat; } + private class ChatFormatConfigSupplier implements Supplier { + private final String group; + private final ChatType chatType; + + ChatFormatConfigSupplier(String group, ChatType chatType) { + this.group = group; + this.chatType = chatType; + } + + @Override + public String get() { + final String chatKey = chatType.key(); + + final String groupPath = "chat.group-formats." + (group == null ? "Default" : group); + String configFormat = config.getString(groupPath + "." + chatKey, null); + + if (configFormat == null) { + configFormat = config.getString(groupPath, null); + } + + final String formatPath = "chat.format"; + if (configFormat == null) { + configFormat = config.getString(formatPath + "." + chatKey, null); + } + + if (configFormat == null) { + configFormat = config.getString(formatPath, null); + } + + if (configFormat == null) { + configFormat = "&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}"; + } + + configFormat = FormatUtil.replaceFormat(configFormat); + configFormat = configFormat.replace("{DISPLAYNAME}", "%1$s"); + configFormat = configFormat.replace("{MESSAGE}", "%2$s"); + configFormat = configFormat.replace("{GROUP}", "{0}"); + configFormat = configFormat.replace("{WORLD}", "{1}"); + configFormat = configFormat.replace("{WORLDNAME}", "{1}"); + configFormat = configFormat.replace("{SHORTWORLDNAME}", "{2}"); + configFormat = configFormat.replace("{TEAMPREFIX}", "{3}"); + configFormat = configFormat.replace("{TEAMSUFFIX}", "{4}"); + configFormat = configFormat.replace("{TEAMNAME}", "{5}"); + configFormat = configFormat.replace("{PREFIX}", "{6}"); + configFormat = configFormat.replace("{SUFFIX}", "{7}"); + configFormat = configFormat.replace("{USERNAME}", "{8}"); + configFormat = configFormat.replace("{NICKNAME}", "{9}"); + configFormat = "§r".concat(configFormat); + return configFormat; + } + } + + private static class ChatFormats { + + private final Map groupFormats; + private TypedChatFormat defaultFormat; + + ChatFormats() { + defaultFormat = null; + groupFormats = new HashMap<>(); + } + + public String getFormat(String group, ChatType type, Supplier configSupplier) { + // With such a large synchronize block, we synchronize a potential config deserialization + // It does not matter as it needs to be done. It's even better as we ensure to do it once + // TypedChatFormat is also synchronized + synchronized (this) { + final TypedChatFormat typedChatFormat; + if (group == null) { + if (defaultFormat == null) { + defaultFormat = new TypedChatFormat(); + } + typedChatFormat = defaultFormat; + } else { + typedChatFormat = groupFormats.computeIfAbsent(group, s -> new TypedChatFormat()); + } + return typedChatFormat.getFormat(type, configSupplier); + } + } + + public void clear() { + synchronized (this) { + defaultFormat = null; + groupFormats.clear(); + } + } + + } + + private static class TypedChatFormat { + + private final Map typedFormats; + private String defaultFormat; + + TypedChatFormat() { + defaultFormat = null; + typedFormats = new EnumMap<>(ChatType.class); + } + + public String getFormat(ChatType type, Supplier configSupplier) { + final String format; + if (type == null) { + if (defaultFormat == null) { + defaultFormat = configSupplier.get(); + } + format = defaultFormat; + } else { + format = typedFormats.computeIfAbsent(type, c -> configSupplier.get()); + } + return format; + } + + } + @Override public String getWorldAlias(String world) { return worldAliases.getOrDefault(world.toLowerCase(), world); @@ -1811,9 +1914,7 @@ public class Settings implements net.ess3.api.ISettings { private List _getDefaultEnabledConfirmCommands() { final List commands = config.getList("default-enabled-confirm-commands", String.class); - for (int i = 0; i < commands.size(); i++) { - commands.set(i, commands.get(i).toLowerCase()); - } + commands.replaceAll(String::toLowerCase); return commands; } diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java b/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java index 763a76378..3124a93fa 100644 --- a/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java +++ b/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java @@ -31,7 +31,7 @@ public enum ChatType { * *

This type used when local/global chat features are disabled */ - UNKNOWN, + UNKNOWN("normal"), ; private final String key; @@ -40,6 +40,10 @@ public enum ChatType { this.key = name().toLowerCase(Locale.ENGLISH); } + ChatType(final String key) { + this.key = key; + } + /** * @return Lowercase name of the chat type. */ diff --git a/Essentials/src/main/resources/config.yml b/Essentials/src/main/resources/config.yml index cec329546..96bcb82af 100644 --- a/Essentials/src/main/resources/config.yml +++ b/Essentials/src/main/resources/config.yml @@ -914,6 +914,7 @@ chat: # Chat formatting can be done in two ways, you can either define a standard format for all chat. # Or you can give a group specific chat format, to give some extra variation. + # For each of these formats, you can specify a sub format for each chat type. # For more information of chat formatting, check out the wiki: http://wiki.ess3.net/wiki/Chat_Formatting # Note: Using the {PREFIX} and {SUFFIX} placeholders along with {DISPLAYNAME} may cause double prefixes/suffixes to be shown in chat unless add-prefix-suffix is uncommented and set to false. @@ -936,10 +937,22 @@ chat: #format: '&7[{GROUP}]&r {DISPLAYNAME}&7:&r {MESSAGE}' #format: '&7{PREFIX}&r {DISPLAYNAME}&r &7{SUFFIX}&r: {MESSAGE}' + # You can also specify a format for each type of chat. + #format: + # normal: '{WORLDNAME} {DISPLAYNAME}&7:&r {MESSAGE}' + # question: '{WORLDNAME} &4{DISPLAYNAME}&7:&r {MESSAGE}' + # shout: '{WORLDNAME} &c[{GROUP}]&r &4{DISPLAYNAME}&7:&c {MESSAGE}' + + # You can specify a format for each group. group-formats: # default: '{WORLDNAME} {DISPLAYNAME}&7:&r {MESSAGE}' # admins: '{WORLDNAME} &c[{GROUP}]&r {DISPLAYNAME}&7:&c {MESSAGE}' + # You can also specify a format for each type of chat for each group. + # admins: + # question: '{WORLDNAME} &4{DISPLAYNAME}&7:&r {MESSAGE}' + # shout: '{WORLDNAME} &c[{GROUP}]&r &4{DISPLAYNAME}&7:&c {MESSAGE}' + # If you are using group formats make sure to remove the '#' to allow the setting to be read. # Note: Group names are case-sensitive so you must match them up with your permission plugin. diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java index 06986d626..6853d1bf1 100644 --- a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java @@ -76,7 +76,7 @@ public abstract class AbstractChatHandler { // This listener should apply the general chat formatting only...then return control back the event handler event.setMessage(FormatUtil.formatMessage(user, "essentials.chat", event.getMessage())); - if (ChatColor.stripColor(event.getMessage()).length() == 0) { + if (ChatColor.stripColor(event.getMessage()).isEmpty()) { event.setCancelled(true); return; } @@ -91,7 +91,8 @@ public abstract class AbstractChatHandler { final String suffix = FormatUtil.replaceFormat(ess.getPermissionsHandler().getSuffix(player)); final Team team = player.getScoreboard().getPlayerTeam(player); - String format = ess.getSettings().getChatFormat(group); + final ChatType chatType = chat.getType(); + String format = ess.getSettings().getChatFormat(group, chat.getRadius() > 0 && chatType == ChatType.UNKNOWN ? ChatType.LOCAL : chatType); format = format.replace("{0}", group); format = format.replace("{1}", ess.getSettings().getWorldAlias(world)); format = format.replace("{2}", world.substring(0, 1).toUpperCase(Locale.ENGLISH)); @@ -104,7 +105,7 @@ public abstract class AbstractChatHandler { format = format.replace("{9}", nickname == null ? username : nickname); // Local, shout and question chat types are only enabled when there's a valid radius - if (chat.getRadius() > 0 && event.getMessage().length() > 0) { + if (chat.getRadius() > 0 && !event.getMessage().isEmpty()) { if (event.getMessage().length() > 1 && ((chat.getType() == ChatType.SHOUT && event.getMessage().charAt(0) == ess.getSettings().getChatShout()) || (chat.getType() == ChatType.QUESTION && event.getMessage().charAt(0) == ess.getSettings().getChatQuestion()))) { event.setMessage(event.getMessage().substring(1)); } @@ -142,7 +143,7 @@ public abstract class AbstractChatHandler { final User user = chat.getUser(); - if (event.getMessage().length() > 0) { + if (!event.getMessage().isEmpty()) { if (chat.getType() == ChatType.UNKNOWN) { if (!user.isAuthorized("essentials.chat.local")) { user.sendTl("notAllowedToLocal"); @@ -288,7 +289,7 @@ public abstract class AbstractChatHandler { } ChatType getChatType(final User user, final String message) { - if (message.length() == 0) { + if (message.isEmpty()) { //Ignore empty chat events generated by plugins return ChatType.UNKNOWN; }