2022-08-06 23:27:58 +02:00
|
|
|
package com.earth2me.essentials.chat.processing;
|
|
|
|
|
|
|
|
import com.earth2me.essentials.ChargeException;
|
|
|
|
import com.earth2me.essentials.Essentials;
|
|
|
|
import com.earth2me.essentials.Trade;
|
|
|
|
import com.earth2me.essentials.User;
|
|
|
|
import com.earth2me.essentials.chat.EssentialsChat;
|
|
|
|
import com.earth2me.essentials.utils.FormatUtil;
|
|
|
|
import net.ess3.api.events.LocalChatSpyEvent;
|
2023-02-14 00:15:15 +01:00
|
|
|
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;
|
2022-08-06 23:27:58 +02:00
|
|
|
import org.bukkit.ChatColor;
|
|
|
|
import org.bukkit.Location;
|
|
|
|
import org.bukkit.Server;
|
|
|
|
import org.bukkit.World;
|
|
|
|
import org.bukkit.entity.Player;
|
|
|
|
import org.bukkit.event.Listener;
|
|
|
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
|
|
|
import org.bukkit.scoreboard.Team;
|
|
|
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.Locale;
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
|
|
|
|
import static com.earth2me.essentials.I18n.tl;
|
|
|
|
|
|
|
|
public abstract class AbstractChatHandler {
|
|
|
|
|
|
|
|
protected final Essentials ess;
|
|
|
|
protected final EssentialsChat essChat;
|
|
|
|
protected final Server server;
|
|
|
|
protected final ChatProcessingCache cache;
|
|
|
|
|
|
|
|
protected AbstractChatHandler(Essentials ess, EssentialsChat essChat) {
|
|
|
|
this.ess = ess;
|
|
|
|
this.essChat = essChat;
|
|
|
|
this.server = ess.getServer();
|
|
|
|
this.cache = new ChatProcessingCache();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Apply chat formatting from config and from translations according to chat type.
|
|
|
|
* <p>
|
|
|
|
* Handled at {@link org.bukkit.event.EventPriority#LOWEST} on both preview and chat events.
|
|
|
|
*/
|
|
|
|
protected void handleChatFormat(AsyncPlayerChatEvent event) {
|
|
|
|
if (isAborted(event)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final User user = ess.getUser(event.getPlayer());
|
|
|
|
|
|
|
|
if (user == null) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-03 00:08:38 +02:00
|
|
|
// Ensure we're getting the latest display name
|
|
|
|
user.setDisplayNick();
|
|
|
|
|
2022-08-06 23:27:58 +02:00
|
|
|
// Reuse cached IntermediateChat if available
|
2023-03-17 19:54:33 +01:00
|
|
|
ChatProcessingCache.ProcessedChat chat = cache.getProcessedChat(event.getPlayer());
|
2022-08-06 23:27:58 +02:00
|
|
|
if (chat == null) {
|
2023-03-17 19:54:33 +01:00
|
|
|
chat = new ChatProcessingCache.ProcessedChat(user, getChatType(user, event.getMessage()), event.getMessage());
|
|
|
|
cache.setProcessedChat(event.getPlayer(), chat);
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
final long configRadius = ess.getSettings().getChatRadius();
|
|
|
|
chat.setRadius(Math.max(configRadius, 0));
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
event.setCancelled(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final String group = user.getGroup();
|
|
|
|
final String world = user.getWorld().getName();
|
|
|
|
final String username = user.getName();
|
|
|
|
final String nickname = user.getFormattedNickname();
|
|
|
|
|
|
|
|
final Player player = user.getBase();
|
|
|
|
final String prefix = FormatUtil.replaceFormat(ess.getPermissionsHandler().getPrefix(player));
|
|
|
|
final String suffix = FormatUtil.replaceFormat(ess.getPermissionsHandler().getSuffix(player));
|
|
|
|
final Team team = player.getScoreboard().getPlayerTeam(player);
|
|
|
|
|
|
|
|
String format = ess.getSettings().getChatFormat(group);
|
|
|
|
format = format.replace("{0}", group);
|
|
|
|
format = format.replace("{1}", ess.getSettings().getWorldAlias(world));
|
|
|
|
format = format.replace("{2}", world.substring(0, 1).toUpperCase(Locale.ENGLISH));
|
|
|
|
format = format.replace("{3}", team == null ? "" : team.getPrefix());
|
|
|
|
format = format.replace("{4}", team == null ? "" : team.getSuffix());
|
|
|
|
format = format.replace("{5}", team == null ? "" : team.getDisplayName());
|
|
|
|
format = format.replace("{6}", prefix);
|
|
|
|
format = format.replace("{7}", suffix);
|
|
|
|
format = format.replace("{8}", username);
|
|
|
|
format = format.replace("{9}", nickname == null ? username : nickname);
|
|
|
|
|
|
|
|
// Local, shout and question chat types are only enabled when there's a valid radius
|
2022-08-08 23:53:59 +02:00
|
|
|
if (chat.getRadius() > 0 && event.getMessage().length() > 0) {
|
2023-05-27 03:48:10 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-02-14 00:15:15 +01:00
|
|
|
if (chat.getType() == ChatType.UNKNOWN) {
|
2022-08-06 23:27:58 +02:00
|
|
|
format = tl("chatTypeLocal").concat(format);
|
|
|
|
} else {
|
2023-02-14 00:15:15 +01:00
|
|
|
format = tl(chat.getType().key() + "Format", format);
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Long live pointless synchronized blocks!
|
|
|
|
synchronized (format) {
|
|
|
|
event.setFormat(format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle the recipient filtering and permissions checks for local chat, if enabled.
|
|
|
|
* <p>
|
|
|
|
* Runs at {@link org.bukkit.event.EventPriority#NORMAL} priority on submitted chat events only.
|
|
|
|
*/
|
|
|
|
protected void handleChatRecipients(AsyncPlayerChatEvent event) {
|
|
|
|
if (isAborted(event)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-17 19:54:33 +01:00
|
|
|
final ChatProcessingCache.Chat chat = cache.getProcessedChat(event.getPlayer());
|
2022-08-06 23:27:58 +02:00
|
|
|
|
|
|
|
// If local chat is enabled, handle the recipients here; else we have nothing to do
|
|
|
|
if (chat.getRadius() < 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final long radiusSquared = chat.getRadius() * chat.getRadius();
|
|
|
|
|
|
|
|
final User user = chat.getUser();
|
|
|
|
|
2022-08-08 23:53:59 +02:00
|
|
|
if (event.getMessage().length() > 0) {
|
2023-02-14 00:15:15 +01:00
|
|
|
if (chat.getType() == ChatType.UNKNOWN) {
|
2022-08-06 23:27:58 +02:00
|
|
|
if (!user.isAuthorized("essentials.chat.local")) {
|
|
|
|
user.sendMessage(tl("notAllowedToLocal"));
|
|
|
|
event.setCancelled(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive.local"));
|
|
|
|
} else {
|
2023-02-14 00:15:15 +01:00
|
|
|
final String permission = "essentials.chat." + chat.getType().key();
|
2022-08-06 23:27:58 +02:00
|
|
|
|
|
|
|
if (user.isAuthorized(permission)) {
|
2023-02-14 00:15:15 +01:00
|
|
|
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);
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final Location loc = user.getLocation();
|
|
|
|
final World world = loc.getWorld();
|
|
|
|
|
|
|
|
final Set<Player> outList = event.getRecipients();
|
|
|
|
final Set<Player> spyList = new HashSet<>();
|
|
|
|
|
|
|
|
try {
|
|
|
|
outList.add(event.getPlayer());
|
|
|
|
} catch (final UnsupportedOperationException ex) {
|
|
|
|
if (ess.getSettings().isDebug()) {
|
|
|
|
essChat.getLogger().log(Level.INFO, "Plugin triggered custom chat event, local chat handling aborted.", ex);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
final Iterator<Player> it = outList.iterator();
|
|
|
|
while (it.hasNext()) {
|
|
|
|
final Player onlinePlayer = it.next();
|
|
|
|
final User onlineUser = ess.getUser(onlinePlayer);
|
|
|
|
if (!onlineUser.equals(user)) {
|
|
|
|
boolean abort = false;
|
|
|
|
final Location playerLoc = onlineUser.getLocation();
|
|
|
|
if (playerLoc.getWorld() != world) {
|
|
|
|
abort = true;
|
|
|
|
} else {
|
|
|
|
final double delta = playerLoc.distanceSquared(loc);
|
|
|
|
if (delta > radiusSquared) {
|
|
|
|
abort = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (abort) {
|
|
|
|
if (onlineUser.isAuthorized("essentials.chat.spy")) {
|
|
|
|
spyList.add(onlinePlayer);
|
|
|
|
}
|
|
|
|
it.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-14 00:15:15 +01:00
|
|
|
callChatEvent(event, ChatType.LOCAL, chat.getRadius());
|
|
|
|
|
|
|
|
if (event.isCancelled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-06 23:27:58 +02:00
|
|
|
if (outList.size() < 2) {
|
|
|
|
user.sendMessage(tl("localNoOne"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strip local chat prefix to preserve API behaviour
|
|
|
|
final String localPrefix = tl("chatTypeLocal");
|
|
|
|
String baseFormat = event.getFormat();
|
|
|
|
if (event.getFormat().startsWith(localPrefix)) {
|
|
|
|
baseFormat = baseFormat.substring(localPrefix.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
final LocalChatSpyEvent spyEvent = new LocalChatSpyEvent(event.isAsynchronous(), event.getPlayer(), baseFormat, event.getMessage(), spyList);
|
|
|
|
server.getPluginManager().callEvent(spyEvent);
|
|
|
|
|
|
|
|
if (!spyEvent.isCancelled()) {
|
|
|
|
for (final Player onlinePlayer : spyEvent.getRecipients()) {
|
|
|
|
onlinePlayer.sendMessage(String.format(spyEvent.getFormat(), user.getDisplayName(), spyEvent.getMessage()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-14 00:15:15 +01:00
|
|
|
/**
|
|
|
|
* 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());
|
|
|
|
}
|
|
|
|
|
2022-08-06 23:27:58 +02:00
|
|
|
/**
|
|
|
|
* Finalise the formatting stage of chat processing.
|
|
|
|
* <p>
|
|
|
|
* Handled at {@link org.bukkit.event.EventPriority#HIGHEST} during previews, and immediately after
|
|
|
|
* {@link #handleChatFormat(AsyncPlayerChatEvent)} when previews are not available.
|
|
|
|
*/
|
|
|
|
protected void handleChatPostFormat(AsyncPlayerChatEvent event) {
|
2023-03-17 19:54:33 +01:00
|
|
|
if (isAborted(event)) {
|
|
|
|
cache.clearProcessedChat(event.getPlayer());
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run costs for chat and clean up the cached {@link com.earth2me.essentials.chat.processing.ChatProcessingCache.ProcessedChat}
|
|
|
|
*/
|
|
|
|
protected void handleChatSubmit(AsyncPlayerChatEvent event) {
|
|
|
|
if (isAborted(event)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This file should handle charging the user for the action before returning control back
|
|
|
|
charge(event, cache.getProcessedChat(event.getPlayer()));
|
|
|
|
|
|
|
|
cache.clearProcessedChat(event.getPlayer());
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean isAborted(final AsyncPlayerChatEvent event) {
|
|
|
|
return event.isCancelled();
|
|
|
|
}
|
|
|
|
|
2023-02-14 00:15:15 +01:00
|
|
|
ChatType getChatType(final User user, final String message) {
|
2022-08-06 23:27:58 +02:00
|
|
|
if (message.length() == 0) {
|
|
|
|
//Ignore empty chat events generated by plugins
|
2023-02-14 00:15:15 +01:00
|
|
|
return ChatType.UNKNOWN;
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
|
2023-05-27 03:48:10 +02:00
|
|
|
final char shoutPrefix = ess.getSettings().getChatShout();
|
|
|
|
final char questionPrefix = ess.getSettings().getChatQuestion();
|
|
|
|
|
2022-08-06 23:27:58 +02:00
|
|
|
final char prefix = message.charAt(0);
|
2023-05-27 03:48:10 +02:00
|
|
|
final boolean singleChar = message.length() == 1;
|
|
|
|
|
|
|
|
if (singleChar) {
|
2022-08-06 23:27:58 +02:00
|
|
|
if (user.isToggleShout()) {
|
2023-05-27 03:48:10 +02:00
|
|
|
return ChatType.SHOUT;
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
2023-05-27 03:48:10 +02:00
|
|
|
return ChatType.UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prefix == questionPrefix && ess.getSettings().isChatQuestionEnabled()) {
|
|
|
|
return ChatType.QUESTION;
|
|
|
|
} else if (prefix == shoutPrefix || user.isToggleShout()) {
|
|
|
|
return ChatType.SHOUT;
|
2022-08-06 23:27:58 +02:00
|
|
|
} else {
|
2023-02-14 00:15:15 +01:00
|
|
|
return ChatType.UNKNOWN;
|
2022-08-06 23:27:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void charge(final User user, final Trade charge) throws ChargeException {
|
|
|
|
charge.charge(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean charge(final AsyncPlayerChatEvent event, final ChatProcessingCache.ProcessedChat chat) {
|
|
|
|
try {
|
|
|
|
charge(chat.getUser(), chat.getCharge());
|
|
|
|
} catch (final ChargeException e) {
|
|
|
|
ess.showError(chat.getUser().getSource(), e, "\\ chat " + chat.getLongType());
|
|
|
|
event.setCancelled(true);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected interface ChatListener extends Listener {
|
|
|
|
@SuppressWarnings("unused")
|
|
|
|
void onPlayerChat(AsyncPlayerChatEvent event);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|