From 7bd890db1b02417e72ad6be4266e17255d63b4ed Mon Sep 17 00:00:00 2001 From: Sasha Sorokin <10401817+Brawaru@users.noreply.github.com> Date: Sun, 12 Dec 2021 23:23:33 +0400 Subject: [PATCH] Add separate events for local and global chat This commit adds two new events: GlobalChatEvent and LocalChatEvent, which allow other plugin developers to know whether the message sent is a global or local one and act accordingly. If either of those events is cancelled, then the source event is cancelled too. Since all chat-related events share the same structure, a new abstract class ChatEvent is created with change made for LocalChatSpyEvent to use it without breaking its API. --- .../ess3/api/events/LocalChatSpyEvent.java | 97 +------------- .../java/net/essentialsx/api/v2/ChatType.java | 49 +++++++ .../api/v2/events/chat/ChatEvent.java | 123 ++++++++++++++++++ .../api/v2/events/chat/GlobalChatEvent.java | 28 ++++ .../api/v2/events/chat/LocalChatEvent.java | 42 ++++++ .../chat/processing/AbstractChatHandler.java | 65 ++++++--- .../chat/processing/ChatProcessingCache.java | 11 +- 7 files changed, 303 insertions(+), 112 deletions(-) create mode 100644 Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java create mode 100644 Essentials/src/main/java/net/essentialsx/api/v2/events/chat/ChatEvent.java create mode 100644 Essentials/src/main/java/net/essentialsx/api/v2/events/chat/GlobalChatEvent.java create mode 100644 Essentials/src/main/java/net/essentialsx/api/v2/events/chat/LocalChatEvent.java diff --git a/Essentials/src/main/java/net/ess3/api/events/LocalChatSpyEvent.java b/Essentials/src/main/java/net/ess3/api/events/LocalChatSpyEvent.java index ffcc99fcf98..0e1bc73875a 100644 --- a/Essentials/src/main/java/net/ess3/api/events/LocalChatSpyEvent.java +++ b/Essentials/src/main/java/net/ess3/api/events/LocalChatSpyEvent.java @@ -1,11 +1,10 @@ package net.ess3.api.events; +import net.essentialsx.api.v2.ChatType; +import net.essentialsx.api.v2.events.chat.ChatEvent; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import java.util.IllegalFormatException; import java.util.Set; import static com.earth2me.essentials.I18n.tl; @@ -13,103 +12,17 @@ /** * Fired when a player uses local chat. */ -public class LocalChatSpyEvent extends Event implements Cancellable { +public class LocalChatSpyEvent extends ChatEvent { private static final HandlerList handlers = new HandlerList(); - private final Player player; - private final Set recipients; - private boolean cancelled = false; - private String message; - private String format; - public LocalChatSpyEvent(final boolean async, final Player who, final String format, final String message, final Set players) { - super(async); - this.format = tl("chatTypeLocal").concat(tl("chatTypeSpy")).concat(format); - this.message = message; - recipients = players; - player = who; + public LocalChatSpyEvent(final boolean async, final Player player, final String format, final String message, final Set recipients) { + super(async, ChatType.SPY, player, tl("chatTypeLocal").concat(tl("chatTypeSpy")).concat(format), message, recipients); } public static HandlerList getHandlerList() { return handlers; } - /** - * Gets the message that the player is attempting to send. This message will be used with {@link #getFormat()}. - * - * @return Message the player is attempting to send - */ - public String getMessage() { - return message; - } - - /** - * Sets the message that the player will send. This message will be used with {@link #getFormat()}. - * - * @param message New message that the player will send - */ - public void setMessage(final String message) { - this.message = message; - } - - /** - * Gets the format to use to display this chat message to spy recipients. When this event finishes execution, the - * first format parameter is the {@link Player#getDisplayName()} and the second parameter is {@link #getMessage()} - * - * @return {@link String#format(String, Object...)} compatible format string - */ - public String getFormat() { - return format; - } - - /** - * Sets the format to use to display this chat message to spy recipients. When this event finishes execution, the - * first format parameter is the {@link Player#getDisplayName()} and the second parameter is {@link #getMessage()} - * - * @param format {@link String#format(String, Object...)} compatible format string - * @throws IllegalFormatException if the underlying API throws the exception - * @throws NullPointerException if format is null - * @see String#format(String, Object...) - */ - public void setFormat(final String format) throws IllegalFormatException, NullPointerException { - // Oh for a better way to do this! - try { - String.format(format, player, message); - } catch (final RuntimeException ex) { - ex.fillInStackTrace(); - throw ex; - } - - this.format = format; - } - - /** - * Gets a set of recipients that this chat message will be displayed to. - * - * @return All Players who will see this chat message - */ - public Set getRecipients() { - return recipients; - } - - /** - * Returns the player involved in this event - * - * @return Player who is involved in this event - */ - public final Player getPlayer() { - return player; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(final boolean cancel) { - this.cancelled = cancel; - } - @Override public HandlerList getHandlers() { return handlers; diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java b/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java new file mode 100644 index 00000000000..763a7637824 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/ChatType.java @@ -0,0 +1,49 @@ +package net.essentialsx.api.v2; + +import java.util.Locale; + +/** + * Represents chat type for a message + */ +public enum ChatType { + /** + * Message is being sent to global chat as a shout + */ + SHOUT, + + /** + * Message is being sent to global chat as a question + */ + QUESTION, + + /** + * Message is being sent locally + */ + LOCAL, + + /** + * Message is being sent to spy channel + */ + SPY, + + /** + * Chat type is not determined + * + *

This type used when local/global chat features are disabled + */ + UNKNOWN, + ; + + private final String key; + + ChatType() { + this.key = name().toLowerCase(Locale.ENGLISH); + } + + /** + * @return Lowercase name of the chat type. + */ + public String key() { + return key; + } +} diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/ChatEvent.java b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/ChatEvent.java new file mode 100644 index 00000000000..0b732c85816 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/ChatEvent.java @@ -0,0 +1,123 @@ +package net.essentialsx.api.v2.events.chat; + +import net.essentialsx.api.v2.ChatType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +import java.util.IllegalFormatException; +import java.util.Set; + +/** + * This handles common boilerplate for other ChatEvents + */ +public abstract class ChatEvent extends Event implements Cancellable { + private final ChatType chatType; + private final Player player; + private final Set recipients; + private String message; + private String format; + + private boolean cancelled = false; + + public ChatEvent(final boolean async, final ChatType chatType, final Player player, + final String format, final String message, final Set recipients) { + super(async); + + this.chatType = chatType; + this.player = player; + this.format = format; + this.message = message; + this.recipients = recipients; + } + + /** + * Gets the message that the player is attempting to send. This message will be used with + * {@link #getFormat()}. + * + * @return Message the player is attempting to send + */ + public String getMessage() { + return message; + } + + /** + * Sets the message that the player will send. This message will be used with + * {@link #getFormat()}. + * + * @param message New message that the player will send + */ + public void setMessage(final String message) { + this.message = message; + } + + /** + * Gets the format to use to display this chat message. When this event finishes execution, the + * first format parameter is the {@link Player#getDisplayName()} and the second parameter is + * {@link #getMessage()} + * + * @return {@link String#format(String, Object...)} compatible format string + */ + public String getFormat() { + return format; + } + + /** + * Sets the format to use to display this chat message. When this event finishes execution, the + * first format parameter is the {@link Player#getDisplayName()} and the second parameter is + * {@link #getMessage()} + * + * @param format {@link String#format(String, Object...)} compatible format string + * @throws IllegalFormatException if the underlying API throws the exception + * @throws NullPointerException if format is null + * @see String#format(String, Object...) + */ + public void setFormat(final String format) throws IllegalFormatException, NullPointerException { + // Oh for a better way to do this! + try { + String.format(format, player, message); + } catch (final RuntimeException ex) { + ex.fillInStackTrace(); + throw ex; + } + + this.format = format; + } + + /** + * Gets a set of recipients that this chat message will be displayed to. + * + * @return All Players who will see this chat message + */ + public Set getRecipients() { + return recipients; + } + + /** + * Returns the player involved in this event + * + * @return Player who is involved in this event + */ + public final Player getPlayer() { + return player; + } + + /** + * Returns the type of chat this event is fired for + * + * @return Type of chat this event is fired for + */ + public ChatType getChatType() { + return chatType; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } +} diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/GlobalChatEvent.java b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/GlobalChatEvent.java new file mode 100644 index 00000000000..87226182224 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/GlobalChatEvent.java @@ -0,0 +1,28 @@ +package net.essentialsx.api.v2.events.chat; + +import net.essentialsx.api.v2.ChatType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import java.util.Set; + +/** + * Fired when a player uses global chat + */ +public class GlobalChatEvent extends ChatEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + public GlobalChatEvent(final boolean async, final ChatType chatType, final Player player, final String format, final String message, final Set recipients) { + super(async, chatType, player, format, message, recipients); + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/LocalChatEvent.java b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/LocalChatEvent.java new file mode 100644 index 00000000000..53fedbb02c4 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/events/chat/LocalChatEvent.java @@ -0,0 +1,42 @@ +package net.essentialsx.api.v2.events.chat; + +import net.essentialsx.api.v2.ChatType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; + +import java.util.Set; + +/** + * Fired when a player uses local chat + */ +public class LocalChatEvent extends ChatEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final long radius; + + public LocalChatEvent(final boolean async, final Player player, final String format, final String message, final Set recipients, final long radius) { + super(async, ChatType.LOCAL, player, format, message, recipients); + this.radius = radius; + } + + /** + * Returns local chat radius used to calculate recipients of this message. + *

+ *

This is not a radius between players: for that use {@link ChatEvent#getRecipients()} and calculate distance + * to player who sent the message ({@link ChatEvent#getPlayer()}). + * @return Non-squared local chat radius. + */ + public long getRadius() { + return radius; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } +} 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 205fad16545..fb9eadce06b 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 @@ -7,6 +7,10 @@ import com.earth2me.essentials.chat.EssentialsChat; import com.earth2me.essentials.utils.FormatUtil; import net.ess3.api.events.LocalChatSpyEvent; +import net.essentialsx.api.v2.ChatType; +import net.essentialsx.api.v2.events.chat.ChatEvent; +import net.essentialsx.api.v2.events.chat.GlobalChatEvent; +import net.essentialsx.api.v2.events.chat.LocalChatEvent; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Server; @@ -97,7 +101,7 @@ protected void handleChatFormat(AsyncPlayerChatEvent event) { // 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.getType().isEmpty()) { + if (chat.getType() == ChatType.UNKNOWN) { if (user.isToggleShout() && event.getMessage().charAt(0) == ess.getSettings().getChatShout()) { event.setMessage(event.getMessage().substring(1)); } @@ -106,7 +110,7 @@ protected void handleChatFormat(AsyncPlayerChatEvent event) { if (event.getMessage().charAt(0) == ess.getSettings().getChatShout() || (event.getMessage().charAt(0) == ess.getSettings().getChatQuestion() && ess.getSettings().isChatQuestionEnabled())) { event.setMessage(event.getMessage().substring(1)); } - format = tl(chat.getType() + "Format", format); + format = tl(chat.getType().key() + "Format", format); } } @@ -140,7 +144,7 @@ protected void handleChatRecipients(AsyncPlayerChatEvent event) { final User user = chat.getUser(); if (event.getMessage().length() > 0) { - if (chat.getType().isEmpty()) { + if (chat.getType() == ChatType.UNKNOWN) { if (!user.isAuthorized("essentials.chat.local")) { user.sendMessage(tl("notAllowedToLocal")); event.setCancelled(true); @@ -149,15 +153,18 @@ protected void handleChatRecipients(AsyncPlayerChatEvent event) { event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive.local")); } else { - final String permission = "essentials.chat." + chat.getType(); + final String permission = "essentials.chat." + chat.getType().key(); if (user.isAuthorized(permission)) { - event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive." + chat.getType())); - return; + event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive." + chat.getType().key())); + + callChatEvent(event, chat.getType(), null); + } else { + final String chatType = chat.getType().name(); + user.sendMessage(tl("notAllowedTo" + chatType.charAt(0) + chatType.substring(1).toLowerCase(Locale.ENGLISH))); + event.setCancelled(true); } - user.sendMessage(tl("notAllowedTo" + chat.getType().substring(0, 1).toUpperCase(Locale.ENGLISH) + chat.getType().substring(1))); - event.setCancelled(true); return; } } @@ -201,6 +208,12 @@ protected void handleChatRecipients(AsyncPlayerChatEvent event) { } } + callChatEvent(event, ChatType.LOCAL, chat.getRadius()); + + if (event.isCancelled()) { + return; + } + if (outList.size() < 2) { user.sendMessage(tl("localNoOne")); } @@ -222,6 +235,28 @@ protected void handleChatRecipients(AsyncPlayerChatEvent event) { } } + /** + * Re-create type-based chat event from the base chat event, call it and mirror changes back to the base chat event. + * @param event Event based on which a type-based event will be created, and to which changes will be applied. + * @param chatType Chat type which determines which event will be created and called. + * @param radius If chat is a local chat, this is a non-squared radius used to calculate recipients, otherwise {@code null}. + */ + protected void callChatEvent(final AsyncPlayerChatEvent event, final ChatType chatType, final Long radius) { + final ChatEvent chatEvent; + + if (chatType == ChatType.LOCAL) { + chatEvent = new LocalChatEvent(event.isAsynchronous(), event.getPlayer(), event.getFormat(), event.getMessage(), event.getRecipients(), radius); + } else { + chatEvent = new GlobalChatEvent(event.isAsynchronous(), chatType, event.getPlayer(), event.getFormat(), event.getMessage(), event.getRecipients()); + } + + server.getPluginManager().callEvent(chatEvent); + + event.setFormat(chatEvent.getFormat()); + event.setMessage(chatEvent.getMessage()); + event.setCancelled(chatEvent.isCancelled()); + } + /** * Finalise the formatting stage of chat processing. *

@@ -265,24 +300,24 @@ boolean isPlayerChat(final AsyncPlayerChatEvent event) { return event.isAsynchronous(); } - String getChatType(final User user, final String message) { + ChatType getChatType(final User user, final String message) { if (message.length() == 0) { //Ignore empty chat events generated by plugins - return ""; + return ChatType.UNKNOWN; } final char prefix = message.charAt(0); if (prefix == ess.getSettings().getChatShout()) { if (user.isToggleShout()) { - return ""; + return ChatType.UNKNOWN; } - return message.length() > 1 ? "shout" : ""; + return message.length() > 1 ? ChatType.SHOUT : ChatType.UNKNOWN; } else if (ess.getSettings().isChatQuestionEnabled() && prefix == ess.getSettings().getChatQuestion()) { - return message.length() > 1 ? "question" : ""; + return message.length() > 1 ? ChatType.QUESTION : ChatType.UNKNOWN; } else if (user.isToggleShout()) { - return message.length() > 1 ? "shout" : ""; + return message.length() > 1 ? ChatType.SHOUT : ChatType.UNKNOWN; } else { - return ""; + return ChatType.UNKNOWN; } } diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java index 7936ef58699..de3f0cec1dd 100644 --- a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java @@ -5,6 +5,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import net.ess3.api.IEssentials; +import net.essentialsx.api.v2.ChatType; import org.bukkit.entity.Player; import java.util.Collections; @@ -56,11 +57,11 @@ public Chat getIntermediateOrElseProcessedChat(final Player player) { public abstract static class Chat { private final User user; - private final String type; + private final ChatType type; private final String originalMessage; protected long radius; - protected Chat(User user, String type, String originalMessage) { + protected Chat(User user, ChatType type, String originalMessage) { this.user = user; this.type = type; this.originalMessage = originalMessage; @@ -70,7 +71,7 @@ public User getUser() { return user; } - public String getType() { + public ChatType getType() { return type; } @@ -83,7 +84,7 @@ public long getRadius() { } public final String getLongType() { - return type.length() == 0 ? "chat" : "chat-" + type; + return type == ChatType.UNKNOWN ? "chat" : "chat-" + type.key(); } } @@ -117,7 +118,7 @@ public static class IntermediateChat extends Chat { private String messageResult; private String formatResult; - public IntermediateChat(final User user, final String type, final String originalMessage) { + public IntermediateChat(final User user, final ChatType type, final String originalMessage) { super(user, type, originalMessage); }