From 1bf3ecc5b2f35c242eb21981ff0096455e3314a8 Mon Sep 17 00:00:00 2001 From: William Jeffcock Date: Mon, 4 Mar 2024 18:42:42 +0000 Subject: [PATCH 1/4] Refactor channels Signed-off-by: William Jeffcock --- core/src/main/java/tc/oc/pgm/PGMPlugin.java | 10 + core/src/main/java/tc/oc/pgm/api/PGM.java | 3 + .../java/tc/oc/pgm/api/channels/Channel.java | 118 +++++ .../oc/pgm/api/event/ChannelMessageEvent.java | 72 +++ .../oc/pgm/api/integration/Integration.java | 20 + .../tc/oc/pgm/api/setting/SettingKey.java | 10 +- .../java/tc/oc/pgm/channels/AdminChannel.java | 104 ++++ .../tc/oc/pgm/channels/ChannelManager.java | 264 ++++++++++ .../tc/oc/pgm/channels/GlobalChannel.java | 53 ++ .../tc/oc/pgm/channels/MessageChannel.java | 292 +++++++++++ .../java/tc/oc/pgm/channels/TeamChannel.java | 78 +++ .../java/tc/oc/pgm/command/CancelCommand.java | 6 +- .../tc/oc/pgm/command/FreeForAllCommand.java | 7 +- .../tc/oc/pgm/command/MapOrderCommand.java | 12 +- .../java/tc/oc/pgm/command/StartCommand.java | 7 +- .../java/tc/oc/pgm/command/TeamCommand.java | 41 +- .../tc/oc/pgm/command/TimeLimitCommand.java | 7 +- .../java/tc/oc/pgm/command/VotingCommand.java | 16 +- .../oc/pgm/command/util/PGMCommandGraph.java | 7 +- .../java/tc/oc/pgm/goals/TouchableGoal.java | 25 +- .../oc/pgm/listeners/AntiGriefListener.java | 11 +- .../tc/oc/pgm/listeners/ChatDispatcher.java | 463 +----------------- .../java/tc/oc/pgm/listeners/PGMListener.java | 3 +- .../tc/oc/pgm/util/MessageSenderIdentity.java | 44 ++ .../java/tc/oc/pgm/util/channels/Channel.java | 9 - .../pgm/util/event/ChannelMessageEvent.java | 54 -- 26 files changed, 1151 insertions(+), 585 deletions(-) create mode 100644 core/src/main/java/tc/oc/pgm/api/channels/Channel.java create mode 100644 core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/AdminChannel.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/ChannelManager.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/MessageChannel.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/TeamChannel.java create mode 100644 core/src/main/java/tc/oc/pgm/util/MessageSenderIdentity.java delete mode 100644 util/src/main/java/tc/oc/pgm/util/channels/Channel.java delete mode 100644 util/src/main/java/tc/oc/pgm/util/event/ChannelMessageEvent.java diff --git a/core/src/main/java/tc/oc/pgm/PGMPlugin.java b/core/src/main/java/tc/oc/pgm/PGMPlugin.java index 9335ade09e..238275d526 100644 --- a/core/src/main/java/tc/oc/pgm/PGMPlugin.java +++ b/core/src/main/java/tc/oc/pgm/PGMPlugin.java @@ -40,6 +40,7 @@ import tc.oc.pgm.api.match.MatchManager; import tc.oc.pgm.api.module.Module; import tc.oc.pgm.api.module.exception.ModuleLoadException; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.command.util.PGMCommandGraph; import tc.oc.pgm.db.CacheDatastore; import tc.oc.pgm.db.SQLDatastore; @@ -98,6 +99,7 @@ public class PGMPlugin extends JavaPlugin implements PGM, Listener { private NameDecorationRegistry nameDecorationRegistry; private ScheduledExecutorService executorService; private ScheduledExecutorService asyncExecutorService; + private ChannelManager channelManager; private InventoryManager inventoryManager; private AfkTracker afkTracker; @@ -241,6 +243,8 @@ public void onEnable() { asyncExecutorService.scheduleAtFixedRate(new ShouldRestartTask(), 0, 1, TimeUnit.MINUTES); } + channelManager = new ChannelManager(); + registerListeners(); registerCommands(); } @@ -347,6 +351,11 @@ public AfkTracker getAfkTracker() { return afkTracker; } + @Override + public ChannelManager getChannelManager() { + return channelManager; + } + private void registerCommands() { try { new PGMCommandGraph(this); @@ -383,6 +392,7 @@ private void registerListeners() { registerEvents(new MotdListener()); registerEvents(new ServerPingDataListener(matchManager, mapOrder, getLogger())); registerEvents(new JoinLeaveAnnouncer(matchManager)); + registerEvents(channelManager); } private boolean loadInitialMaps() { diff --git a/core/src/main/java/tc/oc/pgm/api/PGM.java b/core/src/main/java/tc/oc/pgm/api/PGM.java index 6aa52778c2..969ec8c921 100644 --- a/core/src/main/java/tc/oc/pgm/api/PGM.java +++ b/core/src/main/java/tc/oc/pgm/api/PGM.java @@ -11,6 +11,7 @@ import tc.oc.pgm.api.map.MapLibrary; import tc.oc.pgm.api.map.MapOrder; import tc.oc.pgm.api.match.MatchManager; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.namedecorations.NameDecorationRegistry; import tc.oc.pgm.tablist.MatchTabManager; import tc.oc.pgm.util.listener.AfkTracker; @@ -41,6 +42,8 @@ public interface PGM extends Plugin { InventoryManager getInventoryManager(); + ChannelManager getChannelManager(); + AfkTracker getAfkTracker(); AtomicReference GLOBAL = new AtomicReference<>(null); diff --git a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java new file mode 100644 index 0000000000..893861394c --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java @@ -0,0 +1,118 @@ +package tc.oc.pgm.api.channels; + +import static net.kyori.adventure.text.Component.text; + +import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.paper.PaperCommandManager; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.event.ChannelMessageEvent; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.Players; +import tc.oc.pgm.util.named.NameStyle; + +public interface Channel { + + default String getDisplayName() { + return getAliases()[0]; + } + + String[] getAliases(); + + @Nullable + default Character getShortcut() { + return null; + } + + default SettingValue getSetting() { + return null; + } + + default boolean supportsRedirect() { + return false; + } + + default boolean canSendMessage(MatchPlayer sender) { + return true; + } + + T getTarget(MatchPlayer sender, Map arguments); + + Collection getViewers(T target); + + default Collection getBroadcastViewers(T target) { + return getViewers(target); + } + + default void sendMessage(ChannelMessageEvent event) {} + + default Component formatMessage(T target, @Nullable MatchPlayer sender, Component message) { + if (sender == null) return message; + return text() + .append(text("<", NamedTextColor.WHITE)) + .append(sender.getName(NameStyle.VERBOSE)) + .append(text(">: ", NamedTextColor.WHITE)) + .append(message) + .build(); + } + + default void registerCommand(PaperCommandManager manager) { + String name = getAliases()[0]; + String[] aliases = new String[getAliases().length - 1]; + System.arraycopy(getAliases(), 1, aliases, 0, aliases.length); + + manager.command( + manager + .commandBuilder(name, aliases) + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + PGM.get().getChannelManager().setChannel(sender, this); + })); + + manager.command( + manager + .commandBuilder(name, aliases) + .argument( + StringArgument.builder("message") + .greedy() + .withSuggestionsProvider(Players::suggestPlayers) + .build()) + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + PGM.get().getChannelManager().process(this, sender, context.asMap()); + })); + } + + default Map processChatShortcut(MatchPlayer sender, String message) { + Map arguments = new HashMap(); + if (message.length() == 1) { + PGM.get().getChannelManager().setChannel(sender, this); + return arguments; + } + + arguments.put("message", message.substring(1).trim()); + return arguments; + } + + default void broadcastMessage(Component component, T target) { + broadcastMessage(component, target, player -> true); + } + + default void broadcastMessage(Component component, T target, Predicate filter) { + Collection viewers = getBroadcastViewers(target); + Component finalMessage = formatMessage(target, null, component); + viewers.stream().filter(filter).forEach(player -> player.sendMessage(finalMessage)); + } +} diff --git a/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java b/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java new file mode 100644 index 0000000000..b8472dcf3e --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java @@ -0,0 +1,72 @@ +package tc.oc.pgm.api.event; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.util.event.PreemptiveEvent; + +public class ChannelMessageEvent extends PreemptiveEvent { + + private final Channel channel; + private @Nullable MatchPlayer sender; + private final T target; + private final List viewers; + private String message; + + public ChannelMessageEvent( + Channel channel, + @Nullable MatchPlayer sender, + T target, + Collection viewers, + String message) { + this.channel = channel; + this.sender = sender; + this.target = target; + this.viewers = new ArrayList(viewers); + this.message = message; + } + + public Channel getChannel() { + return channel; + } + + @Nullable + public MatchPlayer getSender() { + return sender; + } + + public void setSender(@Nullable MatchPlayer sender) { + this.sender = sender; + } + + public T getTarget() { + return target; + } + + public List getViewers() { + return viewers; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/core/src/main/java/tc/oc/pgm/api/integration/Integration.java b/core/src/main/java/tc/oc/pgm/api/integration/Integration.java index aa1115c6fa..5e1a5fec4f 100644 --- a/core/src/main/java/tc/oc/pgm/api/integration/Integration.java +++ b/core/src/main/java/tc/oc/pgm/api/integration/Integration.java @@ -3,10 +3,14 @@ import static tc.oc.pgm.util.Assert.assertNotNull; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.player.MatchPlayer; public final class Integration { @@ -23,6 +27,7 @@ private Integration() {} new AtomicReference(new NoopVanishIntegration()); private static final AtomicReference SQUAD = new AtomicReference<>(new NoopSquadIntegration()); + private static Set> CHANNELS = new HashSet>(); public static void setFriendIntegration(FriendIntegration integration) { FRIENDS.set(assertNotNull(integration)); @@ -44,6 +49,13 @@ public static void setSquadIntegration(SquadIntegration integration) { SQUAD.set(assertNotNull(integration)); } + public static void registerChannel(Channel channel) { + if (CHANNELS == null) + throw new IllegalStateException( + "New channels cannot be registered after ChannelManager has been initialised!"); + CHANNELS.add(assertNotNull(channel)); + } + public static boolean isFriend(Player a, Player b) { return FRIENDS.get().isFriend(a, b); } @@ -83,6 +95,14 @@ public static boolean isDisguised(Player player) { return isVanished(player) || getNick(player) != null; } + public static Set> getRegisteredChannels() { + return Collections.unmodifiableSet(CHANNELS); + } + + public static void finishRegisteringChannels() { + CHANNELS = null; + } + // No-op Implementations private static class NoopFriendIntegration implements FriendIntegration { diff --git a/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java b/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java index 52dc3f1da1..7c7c7f655e 100644 --- a/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java +++ b/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java @@ -9,6 +9,8 @@ import java.util.List; import org.bukkit.Material; import org.jetbrains.annotations.NotNull; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.modules.PlayerTimeMatchModule; import tc.oc.pgm.util.Aliased; @@ -25,7 +27,12 @@ public enum SettingKey implements Aliased { Materials.SIGN, CHAT_TEAM, CHAT_GLOBAL, - CHAT_ADMIN), // Changes the default chat channel + CHAT_ADMIN) { + @Override + public void update(MatchPlayer player) { + PGM.get().getChannelManager().setChannel(player, player.getSettings().getValue(CHAT)); + } + }, // Changes the default chat channel DEATH( Arrays.asList("death", "dms"), Materials.SKULL, @@ -87,7 +94,6 @@ public void update(MatchPlayer player) { PlayerTimeMatchModule.updatePlayerTime(player); } }; // Changes player preference for time of day - ; private final List aliases; private final SettingValue[] values; diff --git a/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java new file mode 100644 index 0000000000..c1479126cd --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java @@ -0,0 +1,104 @@ +package tc.oc.pgm.channels; + +import static net.kyori.adventure.key.Key.key; +import static net.kyori.adventure.sound.Sound.sound; +import static net.kyori.adventure.text.Component.empty; +import static net.kyori.adventure.text.Component.text; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.Permissions; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.event.ChannelMessageEvent; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingKey; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.named.NameStyle; + +public class AdminChannel implements Channel { + + private static final TextComponent PREFIX = + text() + .append(text("[", NamedTextColor.WHITE)) + .append(text("A", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.WHITE)) + .build(); + + private static final Sound SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 0.7f); + + @Override + public String getDisplayName() { + return "admin"; + } + + @Override + public String[] getAliases() { + return new String[] {"a"}; + } + + @Override + public Character getShortcut() { + return '$'; + } + + @Override + public SettingValue getSetting() { + return SettingValue.CHAT_ADMIN; + } + + @Override + public boolean canSendMessage(MatchPlayer sender) { + return sender.getBukkit().hasPermission(Permissions.ADMINCHAT); + } + + @Override + public Void getTarget(MatchPlayer sender, Map arguments) { + return null; + } + + @Override + public Collection getViewers(Void unused) { + Set players = new HashSet(); + PGM.get() + .getMatchManager() + .getMatches() + .forEachRemaining( + match -> { + for (MatchPlayer player : match.getPlayers()) + if (player.getBukkit().hasPermission(Permissions.ADMINCHAT)) players.add(player); + }); + return players; + } + + @Override + public void sendMessage(ChannelMessageEvent event) { + for (MatchPlayer viewer : event.getViewers()) { + SettingValue value = viewer.getSettings().getValue(SettingKey.SOUNDS); + if (value.equals(SettingValue.SOUNDS_ALL) || value.equals(SettingValue.SOUNDS_CHAT)) + viewer.playSound(SOUND); + } + } + + @Override + public Component formatMessage(Void target, @Nullable MatchPlayer sender, Component message) { + return text() + .append(PREFIX) + .append( + sender != null + ? text() + .append(sender.getName(NameStyle.VERBOSE)) + .append(text(": ", NamedTextColor.WHITE)) + .build() + : empty()) + .append(message) + .build(); + } +} diff --git a/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java new file mode 100644 index 0000000000..2e4d2f8d9b --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java @@ -0,0 +1,264 @@ +package tc.oc.pgm.channels; + +import static net.kyori.adventure.identity.Identity.identity; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.Component.translatable; +import static tc.oc.pgm.util.text.TextException.exception; +import static tc.oc.pgm.util.text.TextException.noPermission; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerChatTabCompleteEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.event.ChannelMessageEvent; +import tc.oc.pgm.api.integration.Integration; +import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingKey; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.api.setting.Settings; +import tc.oc.pgm.ffa.Tribute; +import tc.oc.pgm.util.Players; +import tc.oc.pgm.util.bukkit.OnlinePlayerMapAdapter; +import tc.oc.pgm.util.text.TextException; + +public class ChannelManager implements Listener { + + private final GlobalChannel globalChannel; + private final AdminChannel adminChannel; + private final TeamChannel teamChannel; + private final Set> channels; + private final Map> shortcuts; + private final OnlinePlayerMapAdapter> selectedChannel; + + private static final Cache CHAT_EVENT_CACHE = + CacheBuilder.newBuilder().weakKeys().expireAfterWrite(15, TimeUnit.SECONDS).build(); + + public ChannelManager() { + this.channels = new HashSet>(); + this.channels.add(globalChannel = new GlobalChannel()); + this.channels.add(adminChannel = new AdminChannel()); + this.channels.add(teamChannel = new TeamChannel()); + this.channels.add(new MessageChannel(this)); + this.channels.addAll(Integration.getRegisteredChannels()); + Integration.finishRegisteringChannels(); + + this.shortcuts = new HashMap>(); + for (Channel channel : channels) { + if (channel.getShortcut() == null) continue; + + this.shortcuts.putIfAbsent(channel.getShortcut(), channel); + } + + this.selectedChannel = new OnlinePlayerMapAdapter>(PGM.get()); + } + + public void processChat(MatchPlayer sender, String message) { + if (message.isEmpty()) return; + + Channel channel = getSelectedChannel(sender); + Map arguments = new HashMap(); + arguments.put("message", message); + + Channel shortcut = shortcuts.get(message.charAt(0)); + if (shortcut != null && shortcut.canSendMessage(sender)) { + arguments = shortcut.processChatShortcut(sender, message); + channel = shortcut; + } + + if (arguments.containsKey("message")) process(channel, sender, arguments); + } + + public void process(Channel channel, MatchPlayer sender, Map arguments) { + process0(calculateChannelRedirect(channel, sender), sender, arguments); + } + + private void process0(Channel channel, MatchPlayer sender, Map arguments) { + if (!channel.canSendMessage(sender)) throw noPermission(); + throwMuted(sender); + + T target = channel.getTarget(sender, arguments); + Collection viewers = channel.getViewers(target); + + final AsyncPlayerChatEvent asyncEvent = + new AsyncPlayerChatEvent( + false, + sender.getBukkit(), + (String) arguments.get("message"), + viewers.stream().map(MatchPlayer::getBukkit).collect(Collectors.toSet())); + CHAT_EVENT_CACHE.put(asyncEvent, true); + sender.getMatch().callEvent(asyncEvent); + if (asyncEvent.isCancelled()) return; + + // The actual message is sent in sendMessage(ChannelMessageEvent) + final ChannelMessageEvent channelEvent = + new ChannelMessageEvent(channel, sender, target, viewers, asyncEvent.getMessage()); + channel.sendMessage(channelEvent); + sender.getMatch().callEvent(channelEvent); + } + + private Channel calculateChannelRedirect(Channel channel, MatchPlayer sender) { + if (Integration.isVanished(sender.getBukkit()) && !(channel instanceof AdminChannel)) { + if (!channel.supportsRedirect()) throw exception("vanish.chat.deny"); + return adminChannel; + } + + if (sender.getMatch().isFinished() || sender.getParty() instanceof Tribute) { + if (channel instanceof TeamChannel) return globalChannel; + } + + return channel; + } + + private void throwMuted(MatchPlayer player) { + if (!Integration.isMuted(player.getBukkit())) return; + Optional muteReason = + Optional.ofNullable(Integration.getMuteReason(player.getBukkit())); + Component reason = + muteReason.isPresent() ? text(muteReason.get()) : translatable("moderation.mute.noReason"); + + throw exception("moderation.mute.message", reason.color(NamedTextColor.AQUA)); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void sendMessage(ChannelMessageEvent event) { + if (event.isCancelled()) { + if (event.getSender() != null && event.getCancellationReason() != null) + event.getSender().sendWarning(event.getCancellationReason()); + return; + } + + Component finalMessage = + event + .getChannel() + .formatMessage(event.getTarget(), event.getSender(), text(event.getMessage())); + Identity senderId = identity(event.getSender().getId()); + event.getViewers().forEach(player -> player.sendMessage(senderId, finalMessage)); + } + + @EventHandler + public void onPlayerTabComplete(PlayerChatTabCompleteEvent event) { + if (event.getChatMessage().trim().equals(event.getLastToken())) { + char first = event.getLastToken().charAt(0); + if (shortcuts.get(first) != null) { + List suggestions = + Players.getPlayerNames(event.getPlayer(), event.getLastToken().substring(1)); + suggestions.replaceAll(s -> first + s); + + event.getTabCompletions().addAll(suggestions); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerJoin(final PlayerJoinEvent event) { + MatchPlayer player = PGM.get().getMatchManager().getPlayer(event.getPlayer()); + if (player == null) return; + selectedChannel.put( + player.getBukkit(), findChannelBySetting(player.getSettings().getValue(SettingKey.CHAT))); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void onChat(AsyncPlayerChatEvent event) { + if (CHAT_EVENT_CACHE.getIfPresent(event) == null) { + event.setCancelled(true); + } else { + CHAT_EVENT_CACHE.invalidate(event); + return; + } + + final MatchPlayer player = PGM.get().getMatchManager().getPlayer(event.getPlayer()); + if (player == null) return; + + final String message = event.getMessage().trim(); + try { + processChat(player, message); + } catch (TextException e) { + // Allow sub-handlers to throw command exceptions just fine + player.sendWarning(e); + } + } + + private Channel findChannelBySetting(SettingValue setting) { + for (Channel channel : channels) if (setting == channel.getSetting()) return channel; + + return globalChannel; + } + + public void setChannel(MatchPlayer player, SettingValue value) { + selectedChannel.put(player.getBukkit(), findChannelBySetting(value)); + } + + public void setChannel(MatchPlayer player, Channel channel) { + Channel previous = selectedChannel.get(player.getBukkit()); + selectedChannel.put(player.getBukkit(), channel); + + if (channel.getSetting() != null) { + Settings setting = player.getSettings(); + final SettingValue old = setting.getValue(SettingKey.CHAT); + + if (old != channel.getSetting()) { + setting.setValue(SettingKey.CHAT, channel.getSetting()); + } + } + + if (previous != channel) { + player.sendMessage( + translatable( + "setting.set", + text("chat"), + text(previous.getDisplayName(), NamedTextColor.GRAY), + text(channel.getDisplayName(), NamedTextColor.GREEN))); + } else { + player.sendMessage( + translatable( + "setting.get", text("chat"), text(previous.getDisplayName(), NamedTextColor.GREEN))); + } + } + + public Channel getSelectedChannel(MatchPlayer player) { + return selectedChannel.getOrDefault(player.getBukkit(), globalChannel); + } + + public Set> getChannels() { + return channels; + } + + public AdminChannel getAdminChannel() { + return adminChannel; + } + + public static void broadcastMessage(Component message) { + PGM.get().getChannelManager().globalChannel.broadcastMessage(message, null); + } + + public static void broadcastMessage(Component message, Predicate filter) { + PGM.get().getChannelManager().globalChannel.broadcastMessage(message, null, filter); + } + + public static void broadcastAdminMessage(Component message) { + PGM.get().getChannelManager().adminChannel.broadcastMessage(message, null); + } + + public static void broadcastPartyMessage(Component message, Party party) { + PGM.get().getChannelManager().teamChannel.broadcastMessage(message, party); + } + + public static void broadcastPartyMessage( + Component message, Party party, Predicate filter) { + PGM.get().getChannelManager().teamChannel.broadcastMessage(message, party, filter); + } +} diff --git a/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java b/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java new file mode 100644 index 0000000000..64a8abd737 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java @@ -0,0 +1,53 @@ +package tc.oc.pgm.channels; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingValue; + +public class GlobalChannel implements Channel { + + @Override + public String getDisplayName() { + return "global"; + } + + @Override + public String[] getAliases() { + return new String[] {"g", "all", "shout"}; + } + + @Override + public Character getShortcut() { + return '!'; + } + + @Override + public SettingValue getSetting() { + return SettingValue.CHAT_GLOBAL; + } + + @Override + public boolean supportsRedirect() { + return true; + } + + @Override + public Void getTarget(MatchPlayer sender, Map arguments) { + return null; + } + + @Override + public Collection getViewers(Void unused) { + Set players = new HashSet(); + PGM.get() + .getMatchManager() + .getMatches() + .forEachRemaining(match -> players.addAll(match.getPlayers())); + return players; + } +} diff --git a/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java b/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java new file mode 100644 index 0000000000..a733fa02b3 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java @@ -0,0 +1,292 @@ +package tc.oc.pgm.channels; + +import static net.kyori.adventure.key.Key.key; +import static net.kyori.adventure.sound.Sound.sound; +import static net.kyori.adventure.text.Component.space; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.Component.translatable; +import static tc.oc.pgm.util.text.TextException.*; + +import cloud.commandframework.arguments.parser.ParserParameters; +import cloud.commandframework.arguments.standard.StringArgument; +import cloud.commandframework.paper.PaperCommandManager; +import io.leangen.geantyref.TypeToken; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.Permissions; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.event.ChannelMessageEvent; +import tc.oc.pgm.api.integration.Integration; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingKey; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.MessageSenderIdentity; +import tc.oc.pgm.util.Players; +import tc.oc.pgm.util.bukkit.OnlinePlayerMapAdapter; +import tc.oc.pgm.util.named.NameStyle; + +public class MessageChannel implements Channel { + + private final ChannelManager manager; + private final OnlinePlayerMapAdapter setChannel, lastMessagedBy; + + private static final Sound SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 1.2f); + + public MessageChannel(ChannelManager manager) { + this.manager = manager; + this.setChannel = new OnlinePlayerMapAdapter(PGM.get()); + this.lastMessagedBy = new OnlinePlayerMapAdapter(PGM.get()); + } + + @Override + public String getDisplayName() { + return "messages"; + } + + @Override + public String[] getAliases() { + return new String[] {"msg", "tell", "r"}; + } + + @Override + public Character getShortcut() { + return '@'; + } + + @Override + public MatchPlayer getTarget(MatchPlayer sender, Map arguments) { + MatchPlayer target = (MatchPlayer) arguments.get("target"); + if (target == null) { + return getSelectedTarget(sender); + } else { + checkSettings(target, sender); + + if (!target.equals(getSelectedTarget(sender)) + && !(manager.getSelectedChannel(sender) instanceof MessageChannel)) { + this.lastMessagedBy.put( + sender.getBukkit(), new MessageSenderIdentity(sender.getBukkit(), target.getBukkit())); + } + + if (!sender.equals(getSelectedTarget(target)) + && !(manager.getSelectedChannel(target) instanceof MessageChannel)) { + this.lastMessagedBy.put( + target.getBukkit(), new MessageSenderIdentity(target.getBukkit(), sender.getBukkit())); + } + + return target; + } + } + + @Override + public Collection getViewers(MatchPlayer target) { + if (target == null) throw exception("command.playerNotFound"); + return Collections.singletonList(target); + } + + @Override + public void sendMessage(ChannelMessageEvent event) { + MatchPlayer sender = event.getSender(); + MatchPlayer target = event.getTarget(); + + SettingValue value = target.getSettings().getValue(SettingKey.SOUNDS); + if (value.equals(SettingValue.SOUNDS_ALL) + || value.equals(SettingValue.SOUNDS_CHAT) + || value.equals(SettingValue.SOUNDS_DM)) target.playSound(SOUND); + + Bukkit.getPluginManager() + .callEvent( + new ChannelMessageEvent( + event.getChannel(), + sender, + event.getTarget(), + Collections.singletonList(sender), + event.getMessage())); + } + + @Override + public Component formatMessage( + MatchPlayer target, @Nullable MatchPlayer sender, Component message) { + if (!target.equals(sender)) + return text() + .append(translatable("misc.to", NamedTextColor.GRAY, TextDecoration.ITALIC)) + .append(space()) + .append(target.getName(NameStyle.VERBOSE)) + .append(text(": ", NamedTextColor.WHITE)) + .append(message) + .build(); + + return text() + .append(translatable("misc.from", NamedTextColor.GRAY, TextDecoration.ITALIC)) + .append(space()) + .append(sender.getName(NameStyle.VERBOSE)) + .append(text(": ", NamedTextColor.WHITE)) + .append(message) + .build(); + } + + @Override + public void registerCommand(PaperCommandManager manager) { + manager.command( + manager + .commandBuilder("msg", "tell") + .argument( + StringArgument.ofType( + TypeToken.get(MatchPlayer.class), "target") + .withParser( + manager + .getParserRegistry() + .createParser( + TypeToken.get(MatchPlayer.class), ParserParameters.empty()) + .orElseThrow(IllegalStateException::new)) + .build()) + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + final MatchPlayer target = context.get("target"); + setSelectedTarget(sender, target); + PGM.get().getChannelManager().setChannel(sender, this); + })); + + manager.command( + manager + .commandBuilder("msg", "tell") + .argument( + StringArgument.ofType( + TypeToken.get(MatchPlayer.class), "target") + .withParser( + manager + .getParserRegistry() + .createParser( + TypeToken.get(MatchPlayer.class), ParserParameters.empty()) + .orElseThrow(IllegalStateException::new)) + .build()) + .argument( + StringArgument.builder("message") + .greedy() + .withSuggestionsProvider(Players::suggestPlayers) + .build()) + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + PGM.get().getChannelManager().process(this, sender, context.asMap()); + })); + + manager.command( + manager + .commandBuilder("reply", "r") + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + MatchPlayer target = getLastMessagedBy(sender); + if (target == null) throw exception("command.message.noReply", text("/msg")); + + setSelectedTarget(sender, target); + PGM.get().getChannelManager().setChannel(sender, this); + })); + + manager.command( + manager + .commandBuilder("reply", "r") + .argument( + StringArgument.builder("message") + .greedy() + .withSuggestionsProvider(Players::suggestPlayers) + .build()) + .handler( + context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + MatchPlayer target = getLastMessagedBy(sender); + if (target == null) throw exception("command.message.noReply", text("/msg")); + context.set("target", target); + + PGM.get().getChannelManager().process(this, sender, context.asMap()); + })); + } + + @Override + public Map processChatShortcut(MatchPlayer sender, String message) { + if (message.length() == 1) throw usage("/msg [message]"); + Map arguments = new HashMap(); + + int spaceIndex = message.indexOf(" "); + MatchPlayer target = + Players.getMatchPlayer( + sender.getBukkit(), + message.substring(1, spaceIndex == -1 ? message.length() : spaceIndex)); + if (target == null) throw exception("command.playerNotFound"); + + if (spaceIndex == -1) { + setSelectedTarget(sender, target); + PGM.get().getChannelManager().setChannel(sender, this); + } else { + arguments.put("message", message.substring(spaceIndex + 1)); + arguments.put("target", target); + } + + return arguments; + } + + private void checkSettings(MatchPlayer target, MatchPlayer sender) { + if (sender.equals(target)) throw exception("command.message.self"); + + SettingValue option = sender.getSettings().getValue(SettingKey.MESSAGE); + if (option.equals(SettingValue.MESSAGE_OFF)) + throw exception("command.message.disabled", text("/toggle dm", NamedTextColor.RED)); + if (option.equals(SettingValue.MESSAGE_FRIEND) + && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) + throw exception("command.message.disabled", text("/toggle dm", NamedTextColor.RED)); + + option = target.getSettings().getValue(SettingKey.MESSAGE); + if (!sender.getBukkit().hasPermission(Permissions.STAFF)) { + if (option.equals(SettingValue.MESSAGE_OFF)) + throw exception("command.message.blocked", target.getName()); + + if (option.equals(SettingValue.MESSAGE_FRIEND) + && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) + throw exception("command.message.friendsOnly", target.getName()); + + if (Integration.isMuted(target.getBukkit())) + throw exception("moderation.mute.target", target.getName()); + } + } + + public void setSelectedTarget(MatchPlayer sender, MatchPlayer target) { + checkSettings(target, sender); + setChannel.put( + sender.getBukkit(), new MessageSenderIdentity(sender.getBukkit(), target.getBukkit())); + } + + public MatchPlayer getSelectedTarget(MatchPlayer sender) { + MessageSenderIdentity targetIdentity = setChannel.get(sender.getBukkit()); + if (targetIdentity == null) return null; + + MatchPlayer target = targetIdentity.getPlayer(sender.getBukkit()); + if (target != null) checkSettings(target, sender); + + return target; + } + + public MatchPlayer getLastMessagedBy(MatchPlayer sender) { + MessageSenderIdentity targetIdentity = lastMessagedBy.get(sender.getBukkit()); + if (targetIdentity == null) return null; + + MatchPlayer target = targetIdentity.getPlayer(sender.getBukkit()); + if (target != null) checkSettings(target, sender); + + return target; + } +} diff --git a/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java new file mode 100644 index 0000000000..ae4c648134 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java @@ -0,0 +1,78 @@ +package tc.oc.pgm.channels; + +import static net.kyori.adventure.text.Component.empty; +import static net.kyori.adventure.text.Component.text; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.jetbrains.annotations.Nullable; +import tc.oc.pgm.api.Permissions; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.named.NameStyle; + +public class TeamChannel implements Channel { + + @Override + public String getDisplayName() { + return "team"; + } + + @Override + public String[] getAliases() { + return new String[] {"t"}; + } + + @Override + public SettingValue getSetting() { + return SettingValue.CHAT_TEAM; + } + + @Override + public boolean supportsRedirect() { + return true; + } + + @Override + public Party getTarget(MatchPlayer sender, Map arguments) { + return sender.getParty(); + } + + @Override + public Collection getViewers(Party target) { + return target.getMatch().getPlayers().stream() + .filter( + viewer -> + target.equals(viewer.getParty()) + || (viewer.isObserving() + && viewer.getBukkit().hasPermission(Permissions.ADMINCHAT))) + .collect(Collectors.toList()); + } + + @Override + public Collection getBroadcastViewers(Party target) { + return target.getMatch().getPlayers().stream() + .filter(viewer -> target.equals(viewer.getParty()) || viewer.isObserving()) + .collect(Collectors.toList()); + } + + @Override + public Component formatMessage(Party target, @Nullable MatchPlayer sender, Component message) { + return text() + .append(target.getChatPrefix()) + .append( + sender != null + ? text() + .append(sender.getName(NameStyle.VERBOSE)) + .append(text(": ", NamedTextColor.WHITE)) + .build() + : empty()) + .append(message) + .build(); + } +} diff --git a/core/src/main/java/tc/oc/pgm/command/CancelCommand.java b/core/src/main/java/tc/oc/pgm/command/CancelCommand.java index 96aa4d7eaf..f505bf7095 100644 --- a/core/src/main/java/tc/oc/pgm/command/CancelCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/CancelCommand.java @@ -10,7 +10,7 @@ import org.incendo.cloud.annotations.Permission; import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.listeners.ChatDispatcher; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.restart.CancelRestartEvent; import tc.oc.pgm.restart.RestartManager; import tc.oc.pgm.start.StartMatchModule; @@ -39,7 +39,7 @@ public void cancel(CommandSender sender, Audience audience, Match match) { match.getCountdown().cancelAll(); match.needModule(StartMatchModule.class).setAutoStart(false); - ChatDispatcher.broadcastAdminChatMessage( - translatable("admin.cancelCountdowns.announce", player(sender, NameStyle.FANCY)), match); + ChannelManager.broadcastAdminMessage( + translatable("admin.cancelCountdowns.announce", player(sender, NameStyle.FANCY))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java b/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java index d3be7c8e24..46408f5a7a 100644 --- a/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java @@ -13,8 +13,8 @@ import org.incendo.cloud.annotations.Permission; import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.ffa.FreeForAllMatchModule; -import tc.oc.pgm.listeners.ChatDispatcher; import tc.oc.pgm.util.named.NameStyle; import tc.oc.pgm.util.text.TextParser; @@ -86,12 +86,11 @@ public void max(Match match, CommandSender sender, FreeForAllMatchModule ffa) { } private void sendResizedMessage(Match match, CommandSender sender, String type, int value) { - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce." + type, player(sender, NameStyle.FANCY), translatable("match.info.players", NamedTextColor.YELLOW), - text(value, NamedTextColor.AQUA)), - match); + text(value, NamedTextColor.AQUA))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java b/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java index 2e37ab821a..6adc25772d 100644 --- a/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java @@ -23,7 +23,7 @@ import tc.oc.pgm.api.map.MapInfo; import tc.oc.pgm.api.map.MapOrder; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.listeners.ChatDispatcher; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.restart.RestartManager; import tc.oc.pgm.util.Audience; import tc.oc.pgm.util.UsernameFormatUtils; @@ -69,13 +69,12 @@ public void setNext( if (mapOrder.getNextMap() != null) { Component mapName = mapOrder.getNextMap().getStyledName(MapNameStyle.COLOR); mapOrder.setNextMap(null); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "map.setNext.revert", NamedTextColor.GRAY, UsernameFormatUtils.formatStaffName(sender, match), - mapName), - match); + mapName)); } else { viewer.sendWarning(translatable("map.noNextMap")); } @@ -94,11 +93,10 @@ public void setNext( public static void sendSetNextMessage(@NotNull MapInfo map, CommandSender sender, Match match) { Component mapName = text(map.getName(), NamedTextColor.GOLD); - Component successful = translatable( + ChannelManager.broadcastAdminMessage( translatable( "map.setNext", NamedTextColor.GRAY, UsernameFormatUtils.formatStaffName(sender, match), - mapName); - ChatDispatcher.broadcastAdminChatMessage(successful, match); + mapName)); } } diff --git a/core/src/main/java/tc/oc/pgm/command/StartCommand.java b/core/src/main/java/tc/oc/pgm/command/StartCommand.java index 334be79756..d7e98aad16 100644 --- a/core/src/main/java/tc/oc/pgm/command/StartCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/StartCommand.java @@ -14,7 +14,7 @@ import org.incendo.cloud.annotations.Permission; import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.listeners.ChatDispatcher; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.start.StartCountdown; import tc.oc.pgm.start.StartMatchModule; import tc.oc.pgm.start.UnreadyReason; @@ -48,11 +48,10 @@ public void start( match.getCountdown().cancelAll(StartCountdown.class); start.forceStartCountdown(duration, null); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "admin.start.announce", player(sender, NameStyle.FANCY), - duration(duration, NamedTextColor.AQUA)), - match); + duration(duration, NamedTextColor.AQUA))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/TeamCommand.java b/core/src/main/java/tc/oc/pgm/command/TeamCommand.java index a89b5e764c..001003a2ee 100644 --- a/core/src/main/java/tc/oc/pgm/command/TeamCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/TeamCommand.java @@ -24,9 +24,9 @@ import tc.oc.pgm.api.party.Competitor; import tc.oc.pgm.api.party.Party; import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.join.JoinMatchModule; import tc.oc.pgm.join.JoinRequest; -import tc.oc.pgm.listeners.ChatDispatcher; import tc.oc.pgm.teams.Team; import tc.oc.pgm.teams.TeamMatchModule; import tc.oc.pgm.util.named.NameStyle; @@ -51,14 +51,13 @@ public void force( } else { join.forceJoin(joiner, (Competitor) team); } - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "join.ok.force.announce", player(sender, NameStyle.FANCY), joiner.getName(NameStyle.FANCY), joiner.getParty().getName(), - oldParty.getName()), - joiner.getMatch()); + oldParty.getName())); } @Command("shuffle") @@ -80,8 +79,8 @@ public void shuffle( teams.forceJoin(player, null); } - ChatDispatcher.broadcastAdminChatMessage( - translatable("match.shuffle.announce.ok", player(sender, NameStyle.FANCY)), match); + ChannelManager.broadcastAdminMessage( + translatable("match.shuffle.announce.ok", player(sender, NameStyle.FANCY))); } @Command("alias ") @@ -106,10 +105,9 @@ public void alias( final Component oldName = team.getName().color(NamedTextColor.GRAY); team.setName(name); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( - "match.alias.announce.ok", player(sender, NameStyle.FANCY), oldName, team.getName()), - match); + "match.alias.announce.ok", player(sender, NameStyle.FANCY), oldName, team.getName())); } @Command("scale ") @@ -125,13 +123,12 @@ public void scale( int maxSize = (int) (team.getMaxPlayers() * scale); team.setMaxSize(maxSize, maxOverfill); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce.max", player(sender, NameStyle.FANCY), team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA)), - match); + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -151,13 +148,12 @@ public void max( else TextParser.assertInRange(maxOverfill, Range.atLeast(maxPlayers)); team.setMaxSize(maxPlayers, maxOverfill); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce.max", player(sender, NameStyle.FANCY), team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA)), - match); + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -167,13 +163,12 @@ public void max( public void max(CommandSender sender, Match match, @Argument("teams") Collection teams) { for (Team team : teams) { team.resetMaxSize(); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce.max", player(sender, NameStyle.FANCY), team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA)), - match); + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -188,13 +183,12 @@ public void min( TextParser.assertInRange(minPlayers, Range.atLeast(0)); for (Team team : teams) { team.setMinSize(minPlayers); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce.min", player(sender, NameStyle.FANCY), team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA)), - match); + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -204,13 +198,12 @@ public void min( public void min(CommandSender sender, Match match, @Argument("teams") Collection teams) { for (Team team : teams) { team.resetMinSize(); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.resize.announce.min", player(sender, NameStyle.FANCY), team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA)), - match); + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } } diff --git a/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java b/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java index 21cee9cb9d..92d9ed50e0 100644 --- a/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java @@ -17,7 +17,7 @@ import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.party.VictoryCondition; -import tc.oc.pgm.listeners.ChatDispatcher; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.timelimit.TimeLimit; import tc.oc.pgm.timelimit.TimeLimitMatchModule; import tc.oc.pgm.util.named.NameStyle; @@ -49,12 +49,11 @@ public void timelimit( true)); time.start(); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "match.timeLimit.announce.commandOutput", player(sender, NameStyle.FANCY), clock(duration).color(NamedTextColor.AQUA), - result.map(r -> r.getDescription(match)).orElse(translatable("misc.unknown"))), - match); + result.map(r -> r.getDescription(match)).orElse(translatable("misc.unknown")))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/VotingCommand.java b/core/src/main/java/tc/oc/pgm/command/VotingCommand.java index 4cde6acce8..6e83389306 100644 --- a/core/src/main/java/tc/oc/pgm/command/VotingCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/VotingCommand.java @@ -24,7 +24,7 @@ import tc.oc.pgm.api.map.MapInfo; import tc.oc.pgm.api.map.MapOrder; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.listeners.ChatDispatcher; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.rotation.MapPoolManager; import tc.oc.pgm.rotation.pools.VotingPool; import tc.oc.pgm.rotation.vote.MapVotePicker; @@ -66,7 +66,7 @@ public void addMap( } if (vote.addMap(map)) { - ChatDispatcher.broadcastAdminChatMessage(addMessage, match); + ChannelManager.broadcastAdminMessage(addMessage); } else { viewer.sendWarning(translatable("vote.limit", NamedTextColor.RED)); } @@ -83,13 +83,12 @@ public void removeMap( @Argument("map") @Greedy MapInfo map) { VotePoolOptions vote = getVoteOptions(mapOrder); if (vote.removeMap(map)) { - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "vote.remove", NamedTextColor.GRAY, UsernameFormatUtils.formatStaffName(sender, match), - map.getStyledName(MapNameStyle.COLOR)), - match); + map.getStyledName(MapNameStyle.COLOR))); } else { viewer.sendWarning(translatable("map.notFound")); } @@ -102,13 +101,12 @@ public void mode(CommandSender sender, MapOrder mapOrder, Match match) { VotePoolOptions vote = getVoteOptions(mapOrder); Component voteModeName = translatable( vote.toggleMode() ? "vote.mode.replace" : "vote.mode.create", NamedTextColor.LIGHT_PURPLE); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "vote.toggle", NamedTextColor.GRAY, UsernameFormatUtils.formatStaffName(sender, match), - voteModeName), - match); + voteModeName)); } @Command("clear") @@ -131,7 +129,7 @@ public void clearMaps(Audience viewer, CommandSender sender, Match match, MapOrd if (maps.isEmpty()) { viewer.sendWarning(translatable("vote.noMapsFound")); } else { - ChatDispatcher.broadcastAdminChatMessage(clearedMsg, match); + ChannelManager.broadcastAdminMessage(clearedMsg); } } diff --git a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java index 0d5111bdff..0669e9ec8d 100644 --- a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java +++ b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java @@ -14,6 +14,7 @@ import tc.oc.pgm.action.actions.ExposedAction; import tc.oc.pgm.api.Config; import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.map.MapInfo; import tc.oc.pgm.api.map.MapLibrary; @@ -80,7 +81,6 @@ import tc.oc.pgm.command.parsers.TeamsParser; import tc.oc.pgm.command.parsers.VariableParser; import tc.oc.pgm.command.parsers.VictoryConditionParser; -import tc.oc.pgm.listeners.ChatDispatcher; import tc.oc.pgm.modes.Mode; import tc.oc.pgm.rotation.MapPoolManager; import tc.oc.pgm.rotation.pools.MapPool; @@ -134,14 +134,15 @@ protected void registerCommands() { if (ShowXmlCommand.isEnabled()) register(ShowXmlCommand.getInstance()); if (PGM.get().getConfiguration().isVanishEnabled()) register(new VanishCommand()); - register(ChatDispatcher.get()); - manager.command(manager .commandBuilder("pgm") .literal("help") .optional("query", StringParser.greedyStringParser()) .handler(context -> minecraftHelp.queryCommands( context.optional("query").orElse(""), context.sender()))); + + for (Channel channel : PGM.get().getChannelManager().getChannels()) + channel.registerCommand(manager); } // Injectors diff --git a/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java b/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java index 92647e3323..fa0bfa683d 100644 --- a/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java +++ b/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java @@ -21,6 +21,7 @@ import tc.oc.pgm.api.party.event.CompetitorRemoveEvent; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.player.ParticipantState; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.goals.events.GoalCompleteEvent; import tc.oc.pgm.goals.events.GoalTouchEvent; import tc.oc.pgm.spawns.events.ParticipantDespawnEvent; @@ -179,10 +180,12 @@ public boolean showEnemyTouches() { return false; } + public boolean shouldShowTouched(@Nullable Competitor team) { + return team != null && !isCompleted(team) && hasTouched(team); + } + public boolean shouldShowTouched(@Nullable Competitor team, Party viewer) { - return team != null - && !isCompleted(team) - && hasTouched(team) + return shouldShowTouched(team) && (team == viewer || showEnemyTouches() || viewer.isObserving()); } @@ -192,15 +195,13 @@ protected void sendTouchMessage(@Nullable ParticipantState toucher, boolean incl Component message = getTouchMessage(toucher, false); Audience.console().sendMessage(message); - if (!showEnemyTouches()) { - message = text().append(toucher.getParty().getChatPrefix()).append(message).build(); - } - - for (MatchPlayer viewer : getMatch().getPlayers()) { - if (shouldShowTouched(toucher.getParty(), viewer.getParty()) - && (toucher == null || !toucher.isPlayer(viewer))) { - viewer.sendMessage(message); - } + if (shouldShowTouched(toucher.getParty())) { + if (showEnemyTouches()) + ChannelManager.broadcastMessage( + message, viewer -> toucher == null || !toucher.isPlayer(viewer)); + else + ChannelManager.broadcastPartyMessage( + message, toucher.getParty(), viewer -> toucher == null || !toucher.isPlayer(viewer)); } if (toucher != null) { diff --git a/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java b/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java index c31bce06b8..6890991b76 100644 --- a/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java +++ b/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java @@ -27,6 +27,7 @@ import tc.oc.pgm.api.match.MatchManager; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.player.ParticipantState; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.spawns.events.ObserverKitApplyEvent; import tc.oc.pgm.tnt.TNTMatchModule; import tc.oc.pgm.tracker.Trackers; @@ -91,25 +92,23 @@ private void participantDefuse(Player player, Entity entity) { entity, translatable("moderation.defuse.player", NamedTextColor.RED, owner.getName())); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "moderation.defuse.alert.player", NamedTextColor.GRAY, clicker.getName(), owner.getName(), - MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED)), - clicker.getMatch()); + MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); } else { this.notifyDefuse( clicker, entity, translatable("moderation.defuse.world", NamedTextColor.RED)); - ChatDispatcher.broadcastAdminChatMessage( + ChannelManager.broadcastAdminMessage( translatable( "moderation.defuse.alert.world", NamedTextColor.GRAY, clicker.getName(), - MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED)), - clicker.getMatch()); + MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); } } } diff --git a/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java b/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java index c9e3588ba7..2918e9ec89 100644 --- a/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java +++ b/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java @@ -1,488 +1,65 @@ package tc.oc.pgm.listeners; -import static net.kyori.adventure.identity.Identity.identity; -import static net.kyori.adventure.text.Component.space; +import static net.kyori.adventure.key.Key.key; +import static net.kyori.adventure.sound.Sound.sound; import static net.kyori.adventure.text.Component.text; -import static net.kyori.adventure.text.Component.translatable; -import static tc.oc.pgm.util.text.TextException.exception; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; import java.util.function.Predicate; -import java.util.stream.Collectors; -import net.kyori.adventure.identity.Identity; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.incendo.cloud.annotation.specifier.Greedy; -import org.incendo.cloud.annotations.Argument; -import org.incendo.cloud.annotations.Command; -import org.incendo.cloud.annotations.CommandDescription; -import org.incendo.cloud.annotations.Permission; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.Permissions; -import tc.oc.pgm.api.integration.Integration; import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.api.match.MatchManager; -import tc.oc.pgm.api.party.Party; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.setting.SettingKey; import tc.oc.pgm.api.setting.SettingValue; -import tc.oc.pgm.command.SettingCommand; -import tc.oc.pgm.ffa.Tribute; -import tc.oc.pgm.util.Audience; -import tc.oc.pgm.util.Players; -import tc.oc.pgm.util.bukkit.BukkitUtils; -import tc.oc.pgm.util.bukkit.Sounds; -import tc.oc.pgm.util.channels.Channel; -import tc.oc.pgm.util.event.ChannelMessageEvent; -import tc.oc.pgm.util.named.NameStyle; -import tc.oc.pgm.util.text.TextException; -import tc.oc.pgm.util.text.TextTranslations; +import tc.oc.pgm.channels.AdminChannel; +import tc.oc.pgm.channels.ChannelManager; -public class ChatDispatcher implements Listener { +@Deprecated() +public class ChatDispatcher { - private static ChatDispatcher INSTANCE = new ChatDispatcher(); + private static final ChatDispatcher INSTANCE = new ChatDispatcher(); public static ChatDispatcher get() { return INSTANCE; // FIXME: no one should need to statically access ChatDispatcher, but community // does this a lot } - private final MatchManager manager; - private final Map lastMessagedBy; - - public static final TextComponent ADMIN_CHAT_PREFIX = text() - .append(text("[", NamedTextColor.WHITE)) - .append(text("A", NamedTextColor.GOLD)) - .append(text("] ", NamedTextColor.WHITE)) - .build(); - - private static final String GLOBAL_SYMBOL = "!"; - private static final String DM_SYMBOL = "@"; - private static final String ADMIN_CHAT_SYMBOL = "$"; + public static final TextComponent ADMIN_CHAT_PREFIX = + text() + .append(text("[", NamedTextColor.WHITE)) + .append(text("A", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.WHITE)) + .build(); - private static final String GLOBAL_FORMAT = "<%s>: %s"; - private static final String PREFIX_FORMAT = "%s: %s"; - private static final String AC_FORMAT = - TextTranslations.translateLegacy(ADMIN_CHAT_PREFIX) + PREFIX_FORMAT; + private static final Sound DM_SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 1.2f); private static final Predicate AC_FILTER = viewer -> viewer.getBukkit().hasPermission(Permissions.ADMINCHAT); - public ChatDispatcher() { - this.manager = PGM.get().getMatchManager(); - this.lastMessagedBy = new HashMap<>(); - PGM.get().getServer().getPluginManager().registerEvents(this, PGM.get()); - } - - public boolean isMuted(MatchPlayer player) { - return player != null && Integration.isMuted(player.getBukkit()); - } - - @Command("g|all [message]") - @CommandDescription("Send a message to everyone") - public void sendGlobal( - Match match, - @NotNull MatchPlayer sender, - @Argument(value = "message", suggestions = "players") @Greedy String message) { - if (Integration.isVanished(sender.getBukkit())) { - sendAdmin(match, sender, message); - return; - } - throwMuted(sender); - - send( - match, - sender, - message, - GLOBAL_FORMAT, - getChatFormat(null, sender, message), - match.getPlayers(), - viewer -> true, - SettingValue.CHAT_GLOBAL, - Channel.GLOBAL); - } - - @Command("t [message]") - @CommandDescription("Send a message to your team") - public void sendTeam( - Match match, - @NotNull MatchPlayer sender, - @Argument(value = "message", suggestions = "players") @Greedy String message) { - if (Integration.isVanished(sender.getBukkit())) { - sendAdmin(match, sender, message); - return; - } - - final Party party = sender.getParty(); - - // No team chat when playing free-for-all or match end, default to global chat - if (party instanceof Tribute || match.isFinished()) { - sendGlobal(match, sender, message); - return; - } - - throwMuted(sender); - send( - match, - sender, - message, - TextTranslations.translateLegacy(party.getChatPrefix()) + PREFIX_FORMAT, - getChatFormat(party.getChatPrefix(), sender, message), - match.getPlayers(), - viewer -> party.equals(viewer.getParty()) - || (viewer.isObserving() && viewer.getBukkit().hasPermission(Permissions.ADMINCHAT)), - SettingValue.CHAT_TEAM, - Channel.TEAM); - } - - @Command("a [message]") - @CommandDescription("Send a message to operators") - @Permission(Permissions.ADMINCHAT) - public void sendAdmin( - Match match, - @NotNull MatchPlayer sender, - @Argument(value = "message", suggestions = "players") @Greedy String message) { - // If a player managed to send a default message without permissions, reset their chat channel - if (!sender.getBukkit().hasPermission(Permissions.ADMINCHAT)) { - sender.getSettings().resetValue(SettingKey.CHAT); - SettingKey.CHAT.update(sender); - sender.sendWarning(translatable("misc.noPermission")); - return; - } - - send( - match, - sender, - message != null ? BukkitUtils.colorize(message) : null, - AC_FORMAT, - getChatFormat(ADMIN_CHAT_PREFIX, sender, message), - match.getPlayers(), - AC_FILTER, - SettingValue.CHAT_ADMIN, - Channel.ADMIN); - - // Play sounds for admin chat - if (message != null) { - match.getPlayers().stream() - .filter(AC_FILTER) // Initial filter - .filter(viewer -> !viewer.equals(sender)) // Don't play sound for sender - .forEach(pl -> playSound(pl, Sounds.ADMIN_CHAT)); - } - } - - @Command("msg|tell|pm|dm ") - @CommandDescription("Send a direct message to a player") - public void sendDirect( - Match match, - @NotNull MatchPlayer sender, - @Argument("player") MatchPlayer receiver, - @Argument(value = "message", suggestions = "players") @Greedy String message) { - if (Integration.isVanished(sender.getBukkit())) throw exception("vanish.chat.deny"); - if (receiver.equals(sender)) throw exception("command.message.self"); - - if (!receiver.getBukkit().hasPermission(Permissions.STAFF)) throwMuted(sender); - - SettingValue option = receiver.getSettings().getValue(SettingKey.MESSAGE); - - if (!sender.getBukkit().hasPermission(Permissions.STAFF)) { - if (option.equals(SettingValue.MESSAGE_OFF)) - throw exception("command.message.blocked", receiver.getName()); - - if (option.equals(SettingValue.MESSAGE_FRIEND) - && !Integration.isFriend(receiver.getBukkit(), sender.getBukkit())) - throw exception("command.message.friendsOnly", receiver.getName()); - - if (isMuted(receiver)) throw exception("moderation.mute.target", receiver.getName()); - } - - trackMessage(receiver.getBukkit(), sender.getBukkit()); - - // Send message to receiver - send( - match, - sender, - message, - formatPrivateMessage("misc.from", receiver.getBukkit()), - getChatFormat( - text() - .append(translatable("misc.from", NamedTextColor.GRAY, TextDecoration.ITALIC)) - .append(space()) - .build(), - sender, - message), - Collections.singleton(receiver), - r -> true, - null, - Channel.PRIVATE_RECEIVER); - - // Send message to the sender - send( - match, - receiver, - message, - formatPrivateMessage("misc.to", sender.getBukkit()), - getChatFormat( - text() - .append(translatable("misc.to", NamedTextColor.GRAY, TextDecoration.ITALIC)) - .append(space()) - .build(), - receiver, - message), - Collections.singleton(sender), - s -> true, - null, - Channel.PRIVATE_SENDER); - playSound(receiver, Sounds.DIRECT_MESSAGE); - } - - private String formatPrivateMessage(String key, CommandSender viewer) { - Component action = - translatable(key, NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true); - return TextTranslations.translateLegacy(action, viewer) + " " + PREFIX_FORMAT; - } - - @Command("reply|r ") - @CommandDescription("Reply to a direct message") - public void sendReply( - Match match, - @NotNull MatchPlayer sender, - @Argument(value = "message", suggestions = "players") @Greedy String message) { - MatchPlayer receiver = manager.getPlayer(getLastMessagedId(sender.getBukkit())); - if (receiver == null) throw exception("command.message.noReply", text("/msg")); - - sendDirect(match, sender, receiver, message); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onChat(AsyncPlayerChatEvent event) { - if (CHAT_EVENT_CACHE.getIfPresent(event) == null) { - event.setCancelled(true); - } else { - CHAT_EVENT_CACHE.invalidate(event); - return; - } - - final MatchPlayer player = manager.getPlayer(event.getPlayer()); - if (player == null) return; - - final String message = event.getMessage(); - - try { - if (message.startsWith(GLOBAL_SYMBOL)) { - sendGlobal(player.getMatch(), player, message.substring(1)); - } else if (message.startsWith(DM_SYMBOL) && message.contains(" ")) { - final String target = message.substring(1, message.indexOf(" ")); - final MatchPlayer receiver = Players.getMatchPlayer(event.getPlayer(), target); - if (receiver == null) { - player.sendWarning(translatable("command.playerNotFound")); - } else { - sendDirect(player.getMatch(), player, receiver, message.substring(2 + target.length())); - } - } else if (message.startsWith(ADMIN_CHAT_SYMBOL) - && player.getBukkit().hasPermission(Permissions.ADMINCHAT)) { - sendAdmin(player.getMatch(), player, event.getMessage().substring(1)); - } else { - sendDefault(player.getMatch(), player, event.getMessage()); - } - } catch (TextException e) { - // Allow sub-handlers to throw command exceptions just fine - player.sendWarning(e); - } - } - - public void sendDefault(Match match, @NotNull MatchPlayer sender, String message) { - switch (sender.getSettings().getValue(SettingKey.CHAT)) { - case CHAT_TEAM: - sendTeam(match, sender, message); - return; - case CHAT_ADMIN: - sendAdmin(match, sender, message); - return; - default: - sendGlobal(match, sender, message); - } - } - - private static final Cache CHAT_EVENT_CACHE = - CacheBuilder.newBuilder() - .weakKeys() - .expireAfterWrite(15, TimeUnit.SECONDS) - .build(); - - public void send( - final @NotNull Match match, - final @NotNull MatchPlayer sender, - final @Nullable String text, - final @NotNull String format, - final @NotNull Component componentMsg, - final @NotNull Collection matchPlayers, - final @NotNull Predicate filter, - final @Nullable SettingValue type, - final @NotNull Channel channel) { - final String message = text == null ? null : text.trim(); - // When a message is empty, this indicates the player wants to change their default chat channel - if (message == null || message.isEmpty()) { - if (type != null) SettingCommand.getInstance().toggle(sender, SettingKey.CHAT, type); - return; - } - - final Set players = matchPlayers.stream() - .filter(filter) - .map(MatchPlayer::getBukkit) - .collect(Collectors.toSet()); - - Runnable completion = - () -> syncSendChat(match, sender, message, format, componentMsg, players, channel); - if (Bukkit.isPrimaryThread()) completion.run(); - else PGM.get().getExecutor().execute(completion); - } - - private void syncSendChat( - final @NotNull Match match, - final @NotNull MatchPlayer sender, - final @NotNull String message, - final @NotNull String format, - final @NotNull Component componentMsg, - final @NotNull Set players, - final @NotNull Channel channel) { - final AsyncPlayerChatEvent event = - new AsyncPlayerChatEvent(false, sender.getBukkit(), message, players); - event.setFormat(format); - CHAT_EVENT_CACHE.put(event, true); - match.callEvent(event); - - if (event.isCancelled()) return; - match.callEvent(new ChannelMessageEvent(channel, sender.getBukkit(), message)); - - Identity senderId = identity(sender.getId()); - - event.getRecipients().stream() - .map(Audience::get) - .forEach(player -> player.sendMessage(senderId, componentMsg)); - } - - private void throwMuted(MatchPlayer player) { - if (isMuted(player)) { - Optional muteReason = - Optional.ofNullable(Integration.getMuteReason(player.getBukkit())); - - Component reason = muteReason.isPresent() - ? text(muteReason.get()) - : translatable("moderation.mute.noReason"); - - throw exception("moderation.mute.message", reason.color(NamedTextColor.AQUA)); - } - } - public static void broadcastAdminChatMessage(Component message, Match match) { - broadcastAdminChatMessage(message, match, Optional.empty()); + ChannelManager.broadcastAdminMessage(message); } public static void broadcastAdminChatMessage( Component message, Match match, Optional sound) { - TextComponent formatted = ADMIN_CHAT_PREFIX.append(message); - match.getPlayers().stream().filter(AC_FILTER).forEach(mp -> { - // If provided a sound, play if setting allows - sound.ifPresent(s -> playSound(mp, s)); - mp.sendMessage(formatted); - }); - Audience.console().sendMessage(formatted); + AdminChannel channel = PGM.get().getChannelManager().getAdminChannel(); + Collection viewers = channel.getBroadcastViewers(null); + channel.broadcastMessage(message, null); + if (sound.isPresent()) viewers.forEach(viewer -> playSound(viewer, sound.get())); } public static void playSound(MatchPlayer player, Sound sound) { SettingValue value = player.getSettings().getValue(SettingKey.SOUNDS); if (value.equals(SettingValue.SOUNDS_ALL) || value.equals(SettingValue.SOUNDS_CHAT) - || (sound.equals(Sounds.DIRECT_MESSAGE) && value.equals(SettingValue.SOUNDS_DM))) { + || (sound.equals(DM_SOUND) && value.equals(SettingValue.SOUNDS_DM))) { player.playSound(sound); } } - - private Component getChatFormat(@Nullable Component prefix, MatchPlayer player, String message) { - Component msg = text(message != null ? message.trim() : ""); - if (prefix == null) - return text() - .append(text("<", NamedTextColor.WHITE)) - .append(player.getName(NameStyle.VERBOSE)) - .append(text(">: ", NamedTextColor.WHITE)) - .append(msg) - .build(); - return text() - .append(prefix) - .append(player.getName(NameStyle.VERBOSE)) - .append(text(": ", NamedTextColor.WHITE)) - .append(msg) - .build(); - } - - @EventHandler(ignoreCancelled = true) - public void onPlayerQuit(PlayerQuitEvent event) { - this.lastMessagedBy.remove(event.getPlayer().getUniqueId()); - } - - private void trackMessage(Player receiver, Player sender) { - this.lastMessagedBy.put(receiver.getUniqueId(), new MessageSenderIdentity(receiver, sender)); - } - - private UUID getLastMessagedId(Player sender) { - MessageSenderIdentity targetIdent = lastMessagedBy.get(sender.getUniqueId()); - if (targetIdent == null) return null; - MatchPlayer target = manager.getPlayer(targetIdent.getPlayerId()); - - // Prevent replying to offline players - if (target == null) return null; - - // Compare last known and current name - String lastKnownName = targetIdent.getName(); - String currentName = Players.getVisibleName(sender, target.getBukkit()); - - // Ensure the target is visible to the viewing sender - boolean visible = Players.isVisible(sender, target.getBukkit()); - - if (currentName.equalsIgnoreCase(lastKnownName) && visible) { - return target.getId(); - } - - return null; - } - - private static class MessageSenderIdentity { - private final UUID playerId; - private final String name; - - public MessageSenderIdentity(Player viewer, Player player) { - this.playerId = player.getUniqueId(); - this.name = Players.getVisibleName(viewer, player); - } - - public UUID getPlayerId() { - return playerId; - } - - public String getName() { - return name; - } - } } diff --git a/core/src/main/java/tc/oc/pgm/listeners/PGMListener.java b/core/src/main/java/tc/oc/pgm/listeners/PGMListener.java index 5b0edc9a65..b1d768b739 100644 --- a/core/src/main/java/tc/oc/pgm/listeners/PGMListener.java +++ b/core/src/main/java/tc/oc/pgm/listeners/PGMListener.java @@ -47,6 +47,7 @@ import tc.oc.pgm.api.match.event.MatchLoadEvent; import tc.oc.pgm.api.match.event.MatchStartEvent; import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.channels.ChannelManager; import tc.oc.pgm.events.MapPoolAdjustEvent; import tc.oc.pgm.events.PlayerJoinMatchEvent; import tc.oc.pgm.events.PlayerLeavePartyEvent; @@ -343,7 +344,7 @@ public void announceDynamicMapPoolChange(MapPoolAdjustEvent event) { forced = translatable("pool.change.forceTimed", poolName, matchLimit, staffName); } - ChatDispatcher.broadcastAdminChatMessage(forced.color(NamedTextColor.GRAY), event.getMatch()); + ChannelManager.broadcastAdminMessage(forced.color(NamedTextColor.GRAY)); } // Broadcast map pool changes due to size diff --git a/core/src/main/java/tc/oc/pgm/util/MessageSenderIdentity.java b/core/src/main/java/tc/oc/pgm/util/MessageSenderIdentity.java new file mode 100644 index 0000000000..6d7671909d --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/util/MessageSenderIdentity.java @@ -0,0 +1,44 @@ +package tc.oc.pgm.util; + +import java.util.UUID; +import org.bukkit.entity.Player; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.player.MatchPlayer; + +public class MessageSenderIdentity { + + private final UUID playerId; + private final String name; + + public MessageSenderIdentity(Player viewer, Player player) { + this.playerId = player.getUniqueId(); + this.name = Players.getVisibleName(viewer, player); + } + + public UUID getPlayerId() { + return playerId; + } + + public String getName() { + return name; + } + + public MatchPlayer getPlayer(Player viewer) { + MatchPlayer target = PGM.get().getMatchManager().getPlayer(playerId); + + // Prevent replying to offline players + if (target == null) return null; + + // Compare last known and current name + String currentName = Players.getVisibleName(viewer, target.getBukkit()); + + // Ensure the target is visible to the viewing sender + boolean visible = Players.isVisible(viewer, target.getBukkit()); + + if (currentName.equalsIgnoreCase(name) && visible) { + return target; + } + + return null; + } +} diff --git a/util/src/main/java/tc/oc/pgm/util/channels/Channel.java b/util/src/main/java/tc/oc/pgm/util/channels/Channel.java deleted file mode 100644 index 73d977fcad..0000000000 --- a/util/src/main/java/tc/oc/pgm/util/channels/Channel.java +++ /dev/null @@ -1,9 +0,0 @@ -package tc.oc.pgm.util.channels; - -public enum Channel { - GLOBAL, - TEAM, - PRIVATE_SENDER, - PRIVATE_RECEIVER, - ADMIN; -} diff --git a/util/src/main/java/tc/oc/pgm/util/event/ChannelMessageEvent.java b/util/src/main/java/tc/oc/pgm/util/event/ChannelMessageEvent.java deleted file mode 100644 index 5995b3f787..0000000000 --- a/util/src/main/java/tc/oc/pgm/util/event/ChannelMessageEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package tc.oc.pgm.util.event; - -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.Nullable; -import tc.oc.pgm.util.channels.Channel; - -public class ChannelMessageEvent extends PreemptiveEvent { - - private Channel channel; - private @Nullable Player sender; - private String message; - - public ChannelMessageEvent(Channel channel, Player sender, String message) { - this.channel = channel; - this.sender = sender; - this.message = message; - } - - public Channel getChannel() { - return channel; - } - - public void setChannel(Channel channel) { - this.channel = channel; - } - - public Player getSender() { - return sender; - } - - public void setSender(Player sender) { - this.sender = sender; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - private static final HandlerList handlers = new HandlerList(); - - @Override - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } -} From 5616aa7217624bcc6cb1ccfa73555304c05787d7 Mon Sep 17 00:00:00 2001 From: Pugzy Date: Mon, 4 Nov 2024 19:49:44 +0000 Subject: [PATCH 2/4] Rebased and refactored --- core/src/main/java/tc/oc/pgm/api/PGM.java | 4 +- .../java/tc/oc/pgm/api/channels/Channel.java | 206 ++++++++---- .../oc/pgm/api/event/ChannelMessageEvent.java | 35 ++- .../oc/pgm/api/integration/Integration.java | 10 +- .../tc/oc/pgm/api/setting/SettingKey.java | 8 +- .../java/tc/oc/pgm/channels/AdminChannel.java | 40 +-- .../tc/oc/pgm/channels/ChannelManager.java | 178 ++++++----- .../tc/oc/pgm/channels/GlobalChannel.java | 31 +- .../tc/oc/pgm/channels/MessageChannel.java | 292 ------------------ .../pgm/channels/PrivateMessageChannel.java | 248 +++++++++++++++ .../java/tc/oc/pgm/channels/TeamChannel.java | 19 +- .../tc/oc/pgm/command/FreeForAllCommand.java | 11 +- .../tc/oc/pgm/command/MapOrderCommand.java | 13 +- .../java/tc/oc/pgm/command/StartCommand.java | 9 +- .../java/tc/oc/pgm/command/TeamCommand.java | 73 ++--- .../tc/oc/pgm/command/TimeLimitCommand.java | 28 +- .../java/tc/oc/pgm/command/VotingCommand.java | 22 +- .../oc/pgm/command/util/PGMCommandGraph.java | 4 +- .../java/tc/oc/pgm/goals/TouchableGoal.java | 17 +- .../oc/pgm/listeners/AntiGriefListener.java | 24 +- .../tc/oc/pgm/listeners/ChatDispatcher.java | 11 +- .../bukkit/OnlinePlayerUUIDMapAdapter.java | 32 ++ 22 files changed, 714 insertions(+), 601 deletions(-) delete mode 100644 core/src/main/java/tc/oc/pgm/channels/MessageChannel.java create mode 100644 core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java create mode 100644 util/src/main/java/tc/oc/pgm/util/bukkit/OnlinePlayerUUIDMapAdapter.java diff --git a/core/src/main/java/tc/oc/pgm/api/PGM.java b/core/src/main/java/tc/oc/pgm/api/PGM.java index 969ec8c921..f6615387ae 100644 --- a/core/src/main/java/tc/oc/pgm/api/PGM.java +++ b/core/src/main/java/tc/oc/pgm/api/PGM.java @@ -42,10 +42,10 @@ public interface PGM extends Plugin { InventoryManager getInventoryManager(); - ChannelManager getChannelManager(); - AfkTracker getAfkTracker(); + ChannelManager getChannelManager(); + AtomicReference GLOBAL = new AtomicReference<>(null); static PGM set(PGM pgm) { diff --git a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java index 893861394c..8bc4c65be7 100644 --- a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java +++ b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java @@ -1,115 +1,207 @@ package tc.oc.pgm.api.channels; -import static net.kyori.adventure.text.Component.text; - -import cloud.commandframework.arguments.standard.StringArgument; -import cloud.commandframework.paper.PaperCommandManager; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.function.Predicate; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.CommandSender; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.meta.CommandMeta; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.SuggestionProvider; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.event.ChannelMessageEvent; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.setting.SettingValue; import tc.oc.pgm.util.Players; -import tc.oc.pgm.util.named.NameStyle; +/** + * Represents a communication channel for handling chat messages + * + * @param the type of the target associated with the channel + */ public interface Channel { + CloudKey MESSAGE_KEY = CloudKey.of("message", String.class); + + /** + * Gets the display name of the channel, defaulting to the first alias. + * + * @return the channel's display name + */ default String getDisplayName() { - return getAliases()[0]; + return getAliases().getFirst(); } - String[] getAliases(); - + /** + * Retrieves the list of aliases for the channel. + * + * @return list of channel aliases + */ + List getAliases(); + + /** + * A character used as a shortcut prefix in messages to specify the target channel. For instance, + * a `!` at the start of a message may indicate global channel messaging, or {@code null} if none. + * + * @return the shortcut character + */ @Nullable default Character getShortcut() { return null; } + /** + * Retrieves the channel's setting value, or {@code null} if none. + * + * @return the setting value + */ default SettingValue getSetting() { return null; } + /** + * If the channel supports message redirection, where messages may be forwarded to another channel + * or location e.g. team messages might be redirected to global chat post match end. + * + * @return {@code true} if redirection is supported, {@code false} otherwise + */ default boolean supportsRedirect() { return false; } + /** + * Checks if a player has permission to send a message in the channel. + * + * @param sender the player sending the message + * @return {@code true} if the player has permission + */ default boolean canSendMessage(MatchPlayer sender) { return true; } - T getTarget(MatchPlayer sender, Map arguments); - + /** + * Retrieves the channel target based on the sender and command context. + * + * @param sender the sender player + * @param arguments command arguments + * @return the target of the channel + */ + T getTarget(MatchPlayer sender, CommandContext arguments); + + /** + * Gets the collection of players viewing messages in this channel for the given target. + * + * @param target the message target + * @return collection of viewers + */ Collection getViewers(T target); + /** + * Gets the players viewing broadcast messages for the given target. + * + * @param target the target audience + * @return collection of broadcast viewers + */ default Collection getBroadcastViewers(T target) { return getViewers(target); } - default void sendMessage(ChannelMessageEvent event) {} - - default Component formatMessage(T target, @Nullable MatchPlayer sender, Component message) { - if (sender == null) return message; - return text() - .append(text("<", NamedTextColor.WHITE)) - .append(sender.getName(NameStyle.VERBOSE)) - .append(text(">: ", NamedTextColor.WHITE)) - .append(message) - .build(); + /** + * Called when a message is sent; allows for additional actions (e.g., sound feedback). + * + * @param event the message event + */ + default void messageSent(final ChannelMessageEvent event) {} + + /** + * Formats a message for this channel. + * + * @param target message target + * @param sender the sender player (optional) + * @param message the content to format + * @return the formatted message + */ + Component formatMessage(T target, @Nullable MatchPlayer sender, Component message); + + /** + * Registers commands using all channel aliases by default unless overrode. + * + * @param manager command manager for command registration + */ + default void registerCommand(CommandManager manager) { + List aliases = getAliases(); + if (aliases.isEmpty()) return; + + manager.command(manager + .commandBuilder(aliases.getFirst(), aliases.subList(1, aliases.size()), CommandMeta.empty()) + .optional( + MESSAGE_KEY, + StringParser.greedyStringParser(), + SuggestionProvider.blockingStrings(Players::suggestPlayers)) + .handler(context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + + if (context.contains(MESSAGE_KEY)) { + PGM.get().getChannelManager().process(this, sender, context); + } else { + PGM.get().getChannelManager().setChannel(sender, this); + } + })); } - default void registerCommand(PaperCommandManager manager) { - String name = getAliases()[0]; - String[] aliases = new String[getAliases().length - 1]; - System.arraycopy(getAliases(), 1, aliases, 0, aliases.length); - - manager.command( - manager - .commandBuilder(name, aliases) - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - PGM.get().getChannelManager().setChannel(sender, this); - })); - - manager.command( - manager - .commandBuilder(name, aliases) - .argument( - StringArgument.builder("message") - .greedy() - .withSuggestionsProvider(Players::suggestPlayers) - .build()) - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - PGM.get().getChannelManager().process(this, sender, context.asMap()); - })); + /** + * Processes a player's chat message in this channel from a {@code AsyncPlayerChatEvent}. Adding + * any context required to the command context store. + * + * @param sender the player + * @param message the message content + * @param context the command context + */ + default void processChatMessage( + MatchPlayer sender, String message, CommandContext context) { + context.store(MESSAGE_KEY, message); } - default Map processChatShortcut(MatchPlayer sender, String message) { - Map arguments = new HashMap(); + /** + * Processes a chat message from a {@code AsyncPlayerChatEvent} with a shortcut character. Adding + * any context required to the command context store. + * + * @param sender the player + * @param message the message starting with a shortcut + * @param context the command context + */ + default void processChatShortcut( + MatchPlayer sender, String message, CommandContext context) { if (message.length() == 1) { PGM.get().getChannelManager().setChannel(sender, this); - return arguments; + return; } - arguments.put("message", message.substring(1).trim()); - return arguments; + context.store(Channel.MESSAGE_KEY, message.substring(1).trim()); } + /** + * Broadcasts a message to all viewers of the target. + * + * @param component the message to broadcast + * @param target the target audience + */ default void broadcastMessage(Component component, T target) { broadcastMessage(component, target, player -> true); } + /** + * Broadcasts a message to viewers, filtered by a predicate. + * + * @param component the message to broadcast + * @param target the target audience + * @param filter predicate to filter recipients + */ default void broadcastMessage(Component component, T target, Predicate filter) { Collection viewers = getBroadcastViewers(target); Component finalMessage = formatMessage(target, null, component); diff --git a/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java b/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java index b8472dcf3e..dc44b0c7b3 100644 --- a/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java +++ b/core/src/main/java/tc/oc/pgm/api/event/ChannelMessageEvent.java @@ -1,10 +1,10 @@ package tc.oc.pgm.api.event; -import java.util.ArrayList; +import static net.kyori.adventure.text.Component.text; + import java.util.Collection; -import java.util.List; +import net.kyori.adventure.text.Component; import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.util.event.PreemptiveEvent; @@ -12,21 +12,22 @@ public class ChannelMessageEvent extends PreemptiveEvent { private final Channel channel; - private @Nullable MatchPlayer sender; + private final MatchPlayer sender; private final T target; - private final List viewers; + private Collection viewers; private String message; + private Component component; public ChannelMessageEvent( Channel channel, - @Nullable MatchPlayer sender, + MatchPlayer sender, T target, Collection viewers, String message) { this.channel = channel; this.sender = sender; this.target = target; - this.viewers = new ArrayList(viewers); + this.viewers = viewers; this.message = message; } @@ -34,29 +35,37 @@ public Channel getChannel() { return channel; } - @Nullable public MatchPlayer getSender() { return sender; } - public void setSender(@Nullable MatchPlayer sender) { - this.sender = sender; - } - public T getTarget() { return target; } - public List getViewers() { + public Collection getViewers() { return viewers; } + public void setViewers(Collection viewers) { + this.viewers = viewers; + } + public String getMessage() { return message; } public void setMessage(String message) { this.message = message; + this.component = null; + } + + public Component getComponent() { + return (component != null) ? component : text(message); + } + + public void setComponent(Component component) { + this.component = component; } private static final HandlerList handlers = new HandlerList(); diff --git a/core/src/main/java/tc/oc/pgm/api/integration/Integration.java b/core/src/main/java/tc/oc/pgm/api/integration/Integration.java index 5e1a5fec4f..6f074d284b 100644 --- a/core/src/main/java/tc/oc/pgm/api/integration/Integration.java +++ b/core/src/main/java/tc/oc/pgm/api/integration/Integration.java @@ -27,7 +27,7 @@ private Integration() {} new AtomicReference(new NoopVanishIntegration()); private static final AtomicReference SQUAD = new AtomicReference<>(new NoopSquadIntegration()); - private static Set> CHANNELS = new HashSet>(); + private static Set> CHANNELS = new HashSet<>(); public static void setFriendIntegration(FriendIntegration integration) { FRIENDS.set(assertNotNull(integration)); @@ -95,12 +95,10 @@ public static boolean isDisguised(Player player) { return isVanished(player) || getNick(player) != null; } - public static Set> getRegisteredChannels() { - return Collections.unmodifiableSet(CHANNELS); - } - - public static void finishRegisteringChannels() { + public static Set> pollRegisteredChannels() { + Set> channels = CHANNELS; CHANNELS = null; + return Collections.unmodifiableSet(channels); } // No-op Implementations diff --git a/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java b/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java index 7c7c7f655e..375e770487 100644 --- a/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java +++ b/core/src/main/java/tc/oc/pgm/api/setting/SettingKey.java @@ -10,7 +10,6 @@ import org.bukkit.Material; import org.jetbrains.annotations.NotNull; import tc.oc.pgm.api.PGM; -import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.modules.PlayerTimeMatchModule; import tc.oc.pgm.util.Aliased; @@ -22,12 +21,7 @@ * @see SettingValue */ public enum SettingKey implements Aliased { - CHAT( - "chat", - Materials.SIGN, - CHAT_TEAM, - CHAT_GLOBAL, - CHAT_ADMIN) { + CHAT("chat", Materials.SIGN, CHAT_TEAM, CHAT_GLOBAL, CHAT_ADMIN) { @Override public void update(MatchPlayer player) { PGM.get().getChannelManager().setChannel(player, player.getSettings().getValue(CHAT)); diff --git a/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java index c1479126cd..2a72c515a3 100644 --- a/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java @@ -7,12 +7,14 @@ import java.util.Collection; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Set; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.context.CommandContext; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.Permissions; @@ -25,12 +27,13 @@ public class AdminChannel implements Channel { - private static final TextComponent PREFIX = - text() - .append(text("[", NamedTextColor.WHITE)) - .append(text("A", NamedTextColor.GOLD)) - .append(text("] ", NamedTextColor.WHITE)) - .build(); + private static final List ALIASES = List.of("a"); + + private static final TextComponent PREFIX = text() + .append(text("[", NamedTextColor.WHITE)) + .append(text("A", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.WHITE)) + .build(); private static final Sound SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 0.7f); @@ -40,8 +43,8 @@ public String getDisplayName() { } @Override - public String[] getAliases() { - return new String[] {"a"}; + public List getAliases() { + return ALIASES; } @Override @@ -60,27 +63,24 @@ public boolean canSendMessage(MatchPlayer sender) { } @Override - public Void getTarget(MatchPlayer sender, Map arguments) { + public Void getTarget(MatchPlayer sender, CommandContext arguments) { return null; } @Override public Collection getViewers(Void unused) { - Set players = new HashSet(); - PGM.get() - .getMatchManager() - .getMatches() - .forEachRemaining( - match -> { - for (MatchPlayer player : match.getPlayers()) - if (player.getBukkit().hasPermission(Permissions.ADMINCHAT)) players.add(player); - }); + Set players = new HashSet<>(); + PGM.get().getMatchManager().getMatches().forEachRemaining(match -> { + for (MatchPlayer player : match.getPlayers()) + if (player.getBukkit().hasPermission(Permissions.ADMINCHAT)) players.add(player); + }); return players; } @Override - public void sendMessage(ChannelMessageEvent event) { + public void messageSent(ChannelMessageEvent event) { for (MatchPlayer viewer : event.getViewers()) { + if (viewer.equals(event.getSender())) continue; SettingValue value = viewer.getSettings().getValue(SettingKey.SOUNDS); if (value.equals(SettingValue.SOUNDS_ALL) || value.equals(SettingValue.SOUNDS_CHAT)) viewer.playSound(SOUND); diff --git a/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java index 2e4d2f8d9b..f4bfbd97b9 100644 --- a/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java +++ b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java @@ -1,6 +1,5 @@ package tc.oc.pgm.channels; -import static net.kyori.adventure.identity.Identity.identity; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.translatable; import static tc.oc.pgm.util.text.TextException.exception; @@ -8,19 +7,27 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; -import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerChatTabCompleteEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.context.CommandContext; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.event.ChannelMessageEvent; @@ -32,91 +39,129 @@ import tc.oc.pgm.api.setting.Settings; import tc.oc.pgm.ffa.Tribute; import tc.oc.pgm.util.Players; -import tc.oc.pgm.util.bukkit.OnlinePlayerMapAdapter; +import tc.oc.pgm.util.bukkit.OnlinePlayerUUIDMapAdapter; import tc.oc.pgm.util.text.TextException; public class ChannelManager implements Listener { + private static final Cache CHAT_EVENT_CACHE = + CacheBuilder.newBuilder() + .weakKeys() + .expireAfterWrite(15, TimeUnit.SECONDS) + .build(); + private final GlobalChannel globalChannel; private final AdminChannel adminChannel; private final TeamChannel teamChannel; private final Set> channels; private final Map> shortcuts; - private final OnlinePlayerMapAdapter> selectedChannel; + private final OnlinePlayerUUIDMapAdapter> selectedChannel; - private static final Cache CHAT_EVENT_CACHE = - CacheBuilder.newBuilder().weakKeys().expireAfterWrite(15, TimeUnit.SECONDS).build(); + private CommandManager manager; public ChannelManager() { - this.channels = new HashSet>(); + this.channels = new HashSet<>(); this.channels.add(globalChannel = new GlobalChannel()); this.channels.add(adminChannel = new AdminChannel()); this.channels.add(teamChannel = new TeamChannel()); - this.channels.add(new MessageChannel(this)); - this.channels.addAll(Integration.getRegisteredChannels()); - Integration.finishRegisteringChannels(); + this.channels.add(new PrivateMessageChannel()); + this.channels.addAll(Integration.pollRegisteredChannels()); - this.shortcuts = new HashMap>(); + this.shortcuts = new HashMap<>(); for (Channel channel : channels) { if (channel.getShortcut() == null) continue; this.shortcuts.putIfAbsent(channel.getShortcut(), channel); } - this.selectedChannel = new OnlinePlayerMapAdapter>(PGM.get()); + this.selectedChannel = new OnlinePlayerUUIDMapAdapter<>(PGM.get()); + } + + public void registerCommands(CommandManager manager) { + this.manager = manager; + + for (Channel channel : PGM.get().getChannelManager().getChannels()) { + channel.registerCommand(manager); + } } public void processChat(MatchPlayer sender, String message) { if (message.isEmpty()) return; - Channel channel = getSelectedChannel(sender); - Map arguments = new HashMap(); - arguments.put("message", message); + CommandContext context = new CommandContext<>(sender.getBukkit(), manager); + Channel channel = shortcuts.get(message.charAt(0)); + + if (channel != null && channel.canSendMessage(sender)) { + channel.processChatShortcut(sender, message, context); + } - Channel shortcut = shortcuts.get(message.charAt(0)); - if (shortcut != null && shortcut.canSendMessage(sender)) { - arguments = shortcut.processChatShortcut(sender, message); - channel = shortcut; + if (channel == null) { + channel = getSelectedChannel(sender); + channel.processChatMessage(sender, message, context); } - if (arguments.containsKey("message")) process(channel, sender, arguments); + if (context.contains(Channel.MESSAGE_KEY)) process(channel, sender, context); } - public void process(Channel channel, MatchPlayer sender, Map arguments) { - process0(calculateChannelRedirect(channel, sender), sender, arguments); + public void process( + Channel channel, MatchPlayer sender, CommandContext context) { + processChannelMessage(calculateChannelRedirect(channel, sender, context), sender, context); } - private void process0(Channel channel, MatchPlayer sender, Map arguments) { + private void processChannelMessage( + Channel channel, MatchPlayer sender, CommandContext context) { if (!channel.canSendMessage(sender)) throw noPermission(); throwMuted(sender); - T target = channel.getTarget(sender, arguments); + T target = channel.getTarget(sender, context); Collection viewers = channel.getViewers(target); - final AsyncPlayerChatEvent asyncEvent = - new AsyncPlayerChatEvent( - false, - sender.getBukkit(), - (String) arguments.get("message"), - viewers.stream().map(MatchPlayer::getBukkit).collect(Collectors.toSet())); + final AsyncPlayerChatEvent asyncEvent = new AsyncPlayerChatEvent( + false, + sender.getBukkit(), + context.get(Channel.MESSAGE_KEY), + viewers.stream().map(MatchPlayer::getBukkit).collect(Collectors.toSet())); + CHAT_EVENT_CACHE.put(asyncEvent, true); sender.getMatch().callEvent(asyncEvent); if (asyncEvent.isCancelled()) return; - // The actual message is sent in sendMessage(ChannelMessageEvent) - final ChannelMessageEvent channelEvent = - new ChannelMessageEvent(channel, sender, target, viewers, asyncEvent.getMessage()); - channel.sendMessage(channelEvent); - sender.getMatch().callEvent(channelEvent); + final ChannelMessageEvent event = + new ChannelMessageEvent<>(channel, sender, target, viewers, asyncEvent.getMessage()); + + sender.getMatch().callEvent(event); + + if (event.isCancelled()) { + if (event.getSender() != null && event.getCancellationReason() != null) { + event.getSender().sendWarning(event.getCancellationReason()); + } + return; + } + + Component finalMessage = event + .getChannel() + .formatMessage(event.getTarget(), event.getSender(), event.getComponent()); + event.getViewers().forEach(player -> player.sendMessage(finalMessage)); + + channel.messageSent(event); } - private Channel calculateChannelRedirect(Channel channel, MatchPlayer sender) { + private Channel calculateChannelRedirect( + Channel channel, MatchPlayer sender, CommandContext context) { if (Integration.isVanished(sender.getBukkit()) && !(channel instanceof AdminChannel)) { + // Allow private messaging with players who can see each other + if (channel instanceof PrivateMessageChannel pmc && pmc.canSendVanished(sender, context)) { + return channel; + } + if (!channel.supportsRedirect()) throw exception("vanish.chat.deny"); + return adminChannel; } - if (sender.getMatch().isFinished() || sender.getParty() instanceof Tribute) { + // Try to use global chat when a match has ended + if (channel.supportsRedirect() + && (sender.getMatch().isFinished() || sender.getParty() instanceof Tribute)) { if (channel instanceof TeamChannel) return globalChannel; } @@ -133,27 +178,11 @@ private void throwMuted(MatchPlayer player) { throw exception("moderation.mute.message", reason.color(NamedTextColor.AQUA)); } - @EventHandler(priority = EventPriority.MONITOR) - public void sendMessage(ChannelMessageEvent event) { - if (event.isCancelled()) { - if (event.getSender() != null && event.getCancellationReason() != null) - event.getSender().sendWarning(event.getCancellationReason()); - return; - } - - Component finalMessage = - event - .getChannel() - .formatMessage(event.getTarget(), event.getSender(), text(event.getMessage())); - Identity senderId = identity(event.getSender().getId()); - event.getViewers().forEach(player -> player.sendMessage(senderId, finalMessage)); - } - @EventHandler public void onPlayerTabComplete(PlayerChatTabCompleteEvent event) { if (event.getChatMessage().trim().equals(event.getLastToken())) { char first = event.getLastToken().charAt(0); - if (shortcuts.get(first) != null) { + if (shortcuts.containsKey(first)) { List suggestions = Players.getPlayerNames(event.getPlayer(), event.getLastToken().substring(1)); suggestions.replaceAll(s -> first + s); @@ -168,18 +197,19 @@ public void onPlayerJoin(final PlayerJoinEvent event) { MatchPlayer player = PGM.get().getMatchManager().getPlayer(event.getPlayer()); if (player == null) return; selectedChannel.put( - player.getBukkit(), findChannelBySetting(player.getSettings().getValue(SettingKey.CHAT))); + player.getId(), findChannelBySetting(player.getSettings().getValue(SettingKey.CHAT))); } @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onChat(AsyncPlayerChatEvent event) { - if (CHAT_EVENT_CACHE.getIfPresent(event) == null) { - event.setCancelled(true); - } else { + if (CHAT_EVENT_CACHE.getIfPresent(event) != null) { + // PGM created chat event, ignore it CHAT_EVENT_CACHE.invalidate(event); return; } + event.setCancelled(true); + final MatchPlayer player = PGM.get().getMatchManager().getPlayer(event.getPlayer()); if (player == null) return; @@ -193,18 +223,20 @@ public void onChat(AsyncPlayerChatEvent event) { } private Channel findChannelBySetting(SettingValue setting) { - for (Channel channel : channels) if (setting == channel.getSetting()) return channel; + for (Channel channel : channels) { + if (setting == channel.getSetting()) return channel; + } - return globalChannel; + return teamChannel; } public void setChannel(MatchPlayer player, SettingValue value) { - selectedChannel.put(player.getBukkit(), findChannelBySetting(value)); + selectedChannel.put(player.getId(), findChannelBySetting(value)); } public void setChannel(MatchPlayer player, Channel channel) { - Channel previous = selectedChannel.get(player.getBukkit()); - selectedChannel.put(player.getBukkit(), channel); + Channel previous = selectedChannel.get(player.getId()); + selectedChannel.put(player.getId(), channel); if (channel.getSetting() != null) { Settings setting = player.getSettings(); @@ -216,21 +248,19 @@ public void setChannel(MatchPlayer player, Channel channel) { } if (previous != channel) { - player.sendMessage( - translatable( - "setting.set", - text("chat"), - text(previous.getDisplayName(), NamedTextColor.GRAY), - text(channel.getDisplayName(), NamedTextColor.GREEN))); + player.sendMessage(translatable( + "setting.set", + text("chat"), + text(previous.getDisplayName(), NamedTextColor.GRAY), + text(channel.getDisplayName(), NamedTextColor.GREEN))); } else { - player.sendMessage( - translatable( - "setting.get", text("chat"), text(previous.getDisplayName(), NamedTextColor.GREEN))); + player.sendMessage(translatable( + "setting.get", text("chat"), text(previous.getDisplayName(), NamedTextColor.GREEN))); } } public Channel getSelectedChannel(MatchPlayer player) { - return selectedChannel.getOrDefault(player.getBukkit(), globalChannel); + return selectedChannel.getOrDefault(player.getId(), globalChannel); } public Set> getChannels() { diff --git a/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java b/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java index 64a8abd737..cd65a145a4 100644 --- a/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/GlobalChannel.java @@ -1,24 +1,34 @@ package tc.oc.pgm.channels; +import static net.kyori.adventure.text.Component.text; + import java.util.Collection; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Set; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.context.CommandContext; +import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.named.NameStyle; public class GlobalChannel implements Channel { + private static final List ALIASES = List.of("g", "all", "shout"); + @Override public String getDisplayName() { return "global"; } @Override - public String[] getAliases() { - return new String[] {"g", "all", "shout"}; + public List getAliases() { + return ALIASES; } @Override @@ -37,17 +47,28 @@ public boolean supportsRedirect() { } @Override - public Void getTarget(MatchPlayer sender, Map arguments) { + public Void getTarget(MatchPlayer sender, CommandContext arguments) { return null; } @Override public Collection getViewers(Void unused) { - Set players = new HashSet(); + Set players = new HashSet<>(); PGM.get() .getMatchManager() .getMatches() .forEachRemaining(match -> players.addAll(match.getPlayers())); return players; } + + @Override + public Component formatMessage(Void target, @Nullable MatchPlayer sender, Component message) { + if (sender == null) return message; + return text() + .append(text("<", NamedTextColor.WHITE)) + .append(sender.getName(NameStyle.VERBOSE)) + .append(text(">: ", NamedTextColor.WHITE)) + .append(message) + .build(); + } } diff --git a/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java b/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java deleted file mode 100644 index a733fa02b3..0000000000 --- a/core/src/main/java/tc/oc/pgm/channels/MessageChannel.java +++ /dev/null @@ -1,292 +0,0 @@ -package tc.oc.pgm.channels; - -import static net.kyori.adventure.key.Key.key; -import static net.kyori.adventure.sound.Sound.sound; -import static net.kyori.adventure.text.Component.space; -import static net.kyori.adventure.text.Component.text; -import static net.kyori.adventure.text.Component.translatable; -import static tc.oc.pgm.util.text.TextException.*; - -import cloud.commandframework.arguments.parser.ParserParameters; -import cloud.commandframework.arguments.standard.StringArgument; -import cloud.commandframework.paper.PaperCommandManager; -import io.leangen.geantyref.TypeToken; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.Nullable; -import tc.oc.pgm.api.PGM; -import tc.oc.pgm.api.Permissions; -import tc.oc.pgm.api.channels.Channel; -import tc.oc.pgm.api.event.ChannelMessageEvent; -import tc.oc.pgm.api.integration.Integration; -import tc.oc.pgm.api.player.MatchPlayer; -import tc.oc.pgm.api.setting.SettingKey; -import tc.oc.pgm.api.setting.SettingValue; -import tc.oc.pgm.util.MessageSenderIdentity; -import tc.oc.pgm.util.Players; -import tc.oc.pgm.util.bukkit.OnlinePlayerMapAdapter; -import tc.oc.pgm.util.named.NameStyle; - -public class MessageChannel implements Channel { - - private final ChannelManager manager; - private final OnlinePlayerMapAdapter setChannel, lastMessagedBy; - - private static final Sound SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 1.2f); - - public MessageChannel(ChannelManager manager) { - this.manager = manager; - this.setChannel = new OnlinePlayerMapAdapter(PGM.get()); - this.lastMessagedBy = new OnlinePlayerMapAdapter(PGM.get()); - } - - @Override - public String getDisplayName() { - return "messages"; - } - - @Override - public String[] getAliases() { - return new String[] {"msg", "tell", "r"}; - } - - @Override - public Character getShortcut() { - return '@'; - } - - @Override - public MatchPlayer getTarget(MatchPlayer sender, Map arguments) { - MatchPlayer target = (MatchPlayer) arguments.get("target"); - if (target == null) { - return getSelectedTarget(sender); - } else { - checkSettings(target, sender); - - if (!target.equals(getSelectedTarget(sender)) - && !(manager.getSelectedChannel(sender) instanceof MessageChannel)) { - this.lastMessagedBy.put( - sender.getBukkit(), new MessageSenderIdentity(sender.getBukkit(), target.getBukkit())); - } - - if (!sender.equals(getSelectedTarget(target)) - && !(manager.getSelectedChannel(target) instanceof MessageChannel)) { - this.lastMessagedBy.put( - target.getBukkit(), new MessageSenderIdentity(target.getBukkit(), sender.getBukkit())); - } - - return target; - } - } - - @Override - public Collection getViewers(MatchPlayer target) { - if (target == null) throw exception("command.playerNotFound"); - return Collections.singletonList(target); - } - - @Override - public void sendMessage(ChannelMessageEvent event) { - MatchPlayer sender = event.getSender(); - MatchPlayer target = event.getTarget(); - - SettingValue value = target.getSettings().getValue(SettingKey.SOUNDS); - if (value.equals(SettingValue.SOUNDS_ALL) - || value.equals(SettingValue.SOUNDS_CHAT) - || value.equals(SettingValue.SOUNDS_DM)) target.playSound(SOUND); - - Bukkit.getPluginManager() - .callEvent( - new ChannelMessageEvent( - event.getChannel(), - sender, - event.getTarget(), - Collections.singletonList(sender), - event.getMessage())); - } - - @Override - public Component formatMessage( - MatchPlayer target, @Nullable MatchPlayer sender, Component message) { - if (!target.equals(sender)) - return text() - .append(translatable("misc.to", NamedTextColor.GRAY, TextDecoration.ITALIC)) - .append(space()) - .append(target.getName(NameStyle.VERBOSE)) - .append(text(": ", NamedTextColor.WHITE)) - .append(message) - .build(); - - return text() - .append(translatable("misc.from", NamedTextColor.GRAY, TextDecoration.ITALIC)) - .append(space()) - .append(sender.getName(NameStyle.VERBOSE)) - .append(text(": ", NamedTextColor.WHITE)) - .append(message) - .build(); - } - - @Override - public void registerCommand(PaperCommandManager manager) { - manager.command( - manager - .commandBuilder("msg", "tell") - .argument( - StringArgument.ofType( - TypeToken.get(MatchPlayer.class), "target") - .withParser( - manager - .getParserRegistry() - .createParser( - TypeToken.get(MatchPlayer.class), ParserParameters.empty()) - .orElseThrow(IllegalStateException::new)) - .build()) - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - final MatchPlayer target = context.get("target"); - setSelectedTarget(sender, target); - PGM.get().getChannelManager().setChannel(sender, this); - })); - - manager.command( - manager - .commandBuilder("msg", "tell") - .argument( - StringArgument.ofType( - TypeToken.get(MatchPlayer.class), "target") - .withParser( - manager - .getParserRegistry() - .createParser( - TypeToken.get(MatchPlayer.class), ParserParameters.empty()) - .orElseThrow(IllegalStateException::new)) - .build()) - .argument( - StringArgument.builder("message") - .greedy() - .withSuggestionsProvider(Players::suggestPlayers) - .build()) - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - PGM.get().getChannelManager().process(this, sender, context.asMap()); - })); - - manager.command( - manager - .commandBuilder("reply", "r") - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - MatchPlayer target = getLastMessagedBy(sender); - if (target == null) throw exception("command.message.noReply", text("/msg")); - - setSelectedTarget(sender, target); - PGM.get().getChannelManager().setChannel(sender, this); - })); - - manager.command( - manager - .commandBuilder("reply", "r") - .argument( - StringArgument.builder("message") - .greedy() - .withSuggestionsProvider(Players::suggestPlayers) - .build()) - .handler( - context -> { - MatchPlayer sender = - context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); - MatchPlayer target = getLastMessagedBy(sender); - if (target == null) throw exception("command.message.noReply", text("/msg")); - context.set("target", target); - - PGM.get().getChannelManager().process(this, sender, context.asMap()); - })); - } - - @Override - public Map processChatShortcut(MatchPlayer sender, String message) { - if (message.length() == 1) throw usage("/msg [message]"); - Map arguments = new HashMap(); - - int spaceIndex = message.indexOf(" "); - MatchPlayer target = - Players.getMatchPlayer( - sender.getBukkit(), - message.substring(1, spaceIndex == -1 ? message.length() : spaceIndex)); - if (target == null) throw exception("command.playerNotFound"); - - if (spaceIndex == -1) { - setSelectedTarget(sender, target); - PGM.get().getChannelManager().setChannel(sender, this); - } else { - arguments.put("message", message.substring(spaceIndex + 1)); - arguments.put("target", target); - } - - return arguments; - } - - private void checkSettings(MatchPlayer target, MatchPlayer sender) { - if (sender.equals(target)) throw exception("command.message.self"); - - SettingValue option = sender.getSettings().getValue(SettingKey.MESSAGE); - if (option.equals(SettingValue.MESSAGE_OFF)) - throw exception("command.message.disabled", text("/toggle dm", NamedTextColor.RED)); - if (option.equals(SettingValue.MESSAGE_FRIEND) - && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) - throw exception("command.message.disabled", text("/toggle dm", NamedTextColor.RED)); - - option = target.getSettings().getValue(SettingKey.MESSAGE); - if (!sender.getBukkit().hasPermission(Permissions.STAFF)) { - if (option.equals(SettingValue.MESSAGE_OFF)) - throw exception("command.message.blocked", target.getName()); - - if (option.equals(SettingValue.MESSAGE_FRIEND) - && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) - throw exception("command.message.friendsOnly", target.getName()); - - if (Integration.isMuted(target.getBukkit())) - throw exception("moderation.mute.target", target.getName()); - } - } - - public void setSelectedTarget(MatchPlayer sender, MatchPlayer target) { - checkSettings(target, sender); - setChannel.put( - sender.getBukkit(), new MessageSenderIdentity(sender.getBukkit(), target.getBukkit())); - } - - public MatchPlayer getSelectedTarget(MatchPlayer sender) { - MessageSenderIdentity targetIdentity = setChannel.get(sender.getBukkit()); - if (targetIdentity == null) return null; - - MatchPlayer target = targetIdentity.getPlayer(sender.getBukkit()); - if (target != null) checkSettings(target, sender); - - return target; - } - - public MatchPlayer getLastMessagedBy(MatchPlayer sender) { - MessageSenderIdentity targetIdentity = lastMessagedBy.get(sender.getBukkit()); - if (targetIdentity == null) return null; - - MatchPlayer target = targetIdentity.getPlayer(sender.getBukkit()); - if (target != null) checkSettings(target, sender); - - return target; - } -} diff --git a/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java b/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java new file mode 100644 index 0000000000..949c487174 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java @@ -0,0 +1,248 @@ +package tc.oc.pgm.channels; + +import static net.kyori.adventure.identity.Identity.identity; +import static net.kyori.adventure.key.Key.key; +import static net.kyori.adventure.sound.Sound.sound; +import static net.kyori.adventure.text.Component.space; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.Component.translatable; +import static net.kyori.adventure.text.event.ClickEvent.runCommand; +import static org.incendo.cloud.parser.standard.StringParser.greedyStringParser; +import static tc.oc.pgm.util.text.TextException.exception; +import static tc.oc.pgm.util.text.TextException.usage; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.suggestion.SuggestionProvider; +import tc.oc.pgm.api.PGM; +import tc.oc.pgm.api.Permissions; +import tc.oc.pgm.api.channels.Channel; +import tc.oc.pgm.api.event.ChannelMessageEvent; +import tc.oc.pgm.api.integration.Integration; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.api.setting.SettingKey; +import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.MessageSenderIdentity; +import tc.oc.pgm.util.Players; +import tc.oc.pgm.util.bukkit.OnlinePlayerUUIDMapAdapter; +import tc.oc.pgm.util.named.NameStyle; + +public class PrivateMessageChannel implements Channel { + + private static final List ALIASES = List.of("msg", "tell", "r"); + + private static final CloudKey TARGET_KEY = CloudKey.of("target", MatchPlayer.class); + + private final OnlinePlayerUUIDMapAdapter selectedPlayer, lastMessagedBy; + + private static final Sound SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 1.2f); + + public PrivateMessageChannel() { + this.selectedPlayer = new OnlinePlayerUUIDMapAdapter<>(PGM.get()); + this.lastMessagedBy = new OnlinePlayerUUIDMapAdapter<>(PGM.get()); + } + + @Override + public String getDisplayName() { + return "private messages"; + } + + @Override + public List getAliases() { + return ALIASES; + } + + @Override + public Character getShortcut() { + return '@'; + } + + @Override + public MatchPlayer getTarget(MatchPlayer sender, CommandContext arguments) { + MatchPlayer target = arguments.get(TARGET_KEY); + // Check the user can message the target + checkPermissions(target, sender); + return target; + } + + @Override + public Collection getViewers(MatchPlayer target) { + if (target == null) throw exception("command.playerNotFound"); + return Collections.singletonList(target); + } + + @Override + public void messageSent(ChannelMessageEvent event) { + MatchPlayer sender = Objects.requireNonNull(event.getSender()); + MatchPlayer target = event.getTarget(); + + sender.sendMessage(formatMessage(target, "to", event.getComponent())); + + SettingValue value = target.getSettings().getValue(SettingKey.SOUNDS); + if (value.equals(SettingValue.SOUNDS_ALL) + || value.equals(SettingValue.SOUNDS_CHAT) + || value.equals(SettingValue.SOUNDS_DM)) target.playSound(SOUND); + + setTarget(lastMessagedBy, sender, target); + setTarget(lastMessagedBy, target, sender); + } + + @Override + public Component formatMessage(MatchPlayer target, MatchPlayer sender, Component message) { + return formatMessage(sender, "from", message); + } + + public Component formatMessage(MatchPlayer player, String direction, Component message) { + return text() + .append(translatable("misc." + direction, NamedTextColor.GRAY, TextDecoration.ITALIC)) + .append(space()) + .append(player.getName(NameStyle.VERBOSE)) + .append(text(": ", NamedTextColor.WHITE)) + .append(message) + .build(); + } + + @Override + public void registerCommand(CommandManager manager) { + manager.command(manager + .commandBuilder("msg", "tell") + .required(CommandComponent.builder() + .key(TARGET_KEY) + .commandManager(manager)) + .optional( + MESSAGE_KEY, + greedyStringParser(), + SuggestionProvider.blockingStrings(Players::suggestPlayers)) + .handler(context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + + if (!context.contains(MESSAGE_KEY)) { + setTarget(selectedPlayer, sender, context.get(TARGET_KEY)); + PGM.get().getChannelManager().setChannel(sender, this); + } else { + PGM.get().getChannelManager().process(this, sender, context); + } + })); + + manager.command(manager + .commandBuilder("reply", "r") + .optional( + MESSAGE_KEY, + greedyStringParser(), + SuggestionProvider.blockingStrings(Players::suggestPlayers)) + .handler(context -> { + MatchPlayer sender = + context.inject(MatchPlayer.class).orElseThrow(IllegalStateException::new); + MatchPlayer target = getTarget(lastMessagedBy, sender); + if (target == null) throw exception("command.message.noReply", text("/msg")); + + if (!context.contains(MESSAGE_KEY)) { + setTarget(selectedPlayer, sender, target); + PGM.get().getChannelManager().setChannel(sender, this); + } else { + context.store(TARGET_KEY, target); + PGM.get().getChannelManager().process(this, sender, context); + } + })); + } + + @Override + public void processChatMessage( + MatchPlayer sender, String message, CommandContext context) { + Channel.super.processChatMessage(sender, message, context); + MatchPlayer target = getTarget(selectedPlayer, sender); + if (target != null) context.store(TARGET_KEY, target); + } + + @Override + public void processChatShortcut( + MatchPlayer sender, String message, CommandContext context) { + if (message.length() == 1) throw usage(getShortcut() + " [message]"); + + int spaceIndex = message.indexOf(' '); + MatchPlayer target = Players.getMatchPlayer( + sender.getBukkit(), message.substring(1, spaceIndex == -1 ? message.length() : spaceIndex)); + if (target == null) throw exception("command.playerNotFound"); + + if (spaceIndex == -1) { + setTarget(selectedPlayer, sender, target); + PGM.get().getChannelManager().setChannel(sender, this); + return; + } + + context.store(MESSAGE_KEY, message.substring(spaceIndex + 1)); + context.store(TARGET_KEY, target); + } + + private void checkPermissions(MatchPlayer target, MatchPlayer sender) { + if (sender.equals(target)) throw exception("command.message.self"); + + SettingValue option = sender.getSettings().getValue(SettingKey.MESSAGE); + if (option.equals(SettingValue.MESSAGE_OFF)) + throw exception( + "command.message.disabled", + text("/toggle dm", NamedTextColor.RED).clickEvent(runCommand("/toggle dm"))); + if (option.equals(SettingValue.MESSAGE_FRIEND) + && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) + throw exception( + "command.message.disabled", + text("/toggle dm", NamedTextColor.RED).clickEvent(runCommand("/toggle dm"))); + + option = target.getSettings().getValue(SettingKey.MESSAGE); + if (!sender.getBukkit().hasPermission(Permissions.STAFF)) { + if (option.equals(SettingValue.MESSAGE_OFF)) + throw exception("command.message.blocked", target.getName()); + + if (option.equals(SettingValue.MESSAGE_FRIEND) + && !Integration.isFriend(target.getBukkit(), sender.getBukkit())) + throw exception("command.message.friendsOnly", target.getName()); + + if (Integration.isMuted(target.getBukkit())) + throw exception("moderation.mute.target", target.getName()); + } + } + + public MatchPlayer getTarget( + OnlinePlayerUUIDMapAdapter store, MatchPlayer sender) { + MessageSenderIdentity targetIdentity = store.get(sender.getId()); + if (targetIdentity == null) return null; + + MatchPlayer target = targetIdentity.getPlayer(sender.getBukkit()); + if (target != null) checkPermissions(target, sender); + + return target; + } + + public void setTarget( + OnlinePlayerUUIDMapAdapter store, + MatchPlayer sender, + MatchPlayer target) { + checkPermissions(target, sender); + store.compute(sender.getId(), (uuid, identity) -> { + if (identity != null && identity.getPlayer(sender.getBukkit()) == target) { + return identity; + } + + return new MessageSenderIdentity(sender.getBukkit(), target.getBukkit()); + }); + } + + public boolean canSendVanished(MatchPlayer sender, CommandContext context) { + return context + .optional(TARGET_KEY) + .map(target -> Players.isVisible(target.getBukkit(), sender.getBukkit())) + .orElse(false); + } +} diff --git a/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java index ae4c648134..0955da0e89 100644 --- a/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java @@ -4,10 +4,12 @@ import static net.kyori.adventure.text.Component.text; import java.util.Collection; -import java.util.Map; +import java.util.List; import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.context.CommandContext; import org.jetbrains.annotations.Nullable; import tc.oc.pgm.api.Permissions; import tc.oc.pgm.api.channels.Channel; @@ -18,14 +20,16 @@ public class TeamChannel implements Channel { + private static final List ALIASES = List.of("t"); + @Override public String getDisplayName() { return "team"; } @Override - public String[] getAliases() { - return new String[] {"t"}; + public List getAliases() { + return ALIASES; } @Override @@ -39,18 +43,15 @@ public boolean supportsRedirect() { } @Override - public Party getTarget(MatchPlayer sender, Map arguments) { + public Party getTarget(MatchPlayer sender, CommandContext arguments) { return sender.getParty(); } @Override public Collection getViewers(Party target) { return target.getMatch().getPlayers().stream() - .filter( - viewer -> - target.equals(viewer.getParty()) - || (viewer.isObserving() - && viewer.getBukkit().hasPermission(Permissions.ADMINCHAT))) + .filter(viewer -> target.equals(viewer.getParty()) + || (viewer.isObserving() && viewer.getBukkit().hasPermission(Permissions.ADMINCHAT))) .collect(Collectors.toList()); } diff --git a/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java b/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java index 46408f5a7a..b88297489b 100644 --- a/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/FreeForAllCommand.java @@ -86,11 +86,10 @@ public void max(Match match, CommandSender sender, FreeForAllMatchModule ffa) { } private void sendResizedMessage(Match match, CommandSender sender, String type, int value) { - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce." + type, - player(sender, NameStyle.FANCY), - translatable("match.info.players", NamedTextColor.YELLOW), - text(value, NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce." + type, + player(sender, NameStyle.FANCY), + translatable("match.info.players", NamedTextColor.YELLOW), + text(value, NamedTextColor.AQUA))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java b/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java index 6adc25772d..aa2ca7df1f 100644 --- a/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/MapOrderCommand.java @@ -69,12 +69,11 @@ public void setNext( if (mapOrder.getNextMap() != null) { Component mapName = mapOrder.getNextMap().getStyledName(MapNameStyle.COLOR); mapOrder.setNextMap(null); - ChannelManager.broadcastAdminMessage( - translatable( - "map.setNext.revert", - NamedTextColor.GRAY, - UsernameFormatUtils.formatStaffName(sender, match), - mapName)); + ChannelManager.broadcastAdminMessage(translatable( + "map.setNext.revert", + NamedTextColor.GRAY, + UsernameFormatUtils.formatStaffName(sender, match), + mapName)); } else { viewer.sendWarning(translatable("map.noNextMap")); } @@ -93,7 +92,7 @@ public void setNext( public static void sendSetNextMessage(@NotNull MapInfo map, CommandSender sender, Match match) { Component mapName = text(map.getName(), NamedTextColor.GOLD); - ChannelManager.broadcastAdminMessage( translatable( + ChannelManager.broadcastAdminMessage(translatable( "map.setNext", NamedTextColor.GRAY, UsernameFormatUtils.formatStaffName(sender, match), diff --git a/core/src/main/java/tc/oc/pgm/command/StartCommand.java b/core/src/main/java/tc/oc/pgm/command/StartCommand.java index d7e98aad16..eb94023156 100644 --- a/core/src/main/java/tc/oc/pgm/command/StartCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/StartCommand.java @@ -48,10 +48,9 @@ public void start( match.getCountdown().cancelAll(StartCountdown.class); start.forceStartCountdown(duration, null); - ChannelManager.broadcastAdminMessage( - translatable( - "admin.start.announce", - player(sender, NameStyle.FANCY), - duration(duration, NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "admin.start.announce", + player(sender, NameStyle.FANCY), + duration(duration, NamedTextColor.AQUA))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/TeamCommand.java b/core/src/main/java/tc/oc/pgm/command/TeamCommand.java index 001003a2ee..e1388aaccc 100644 --- a/core/src/main/java/tc/oc/pgm/command/TeamCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/TeamCommand.java @@ -51,13 +51,12 @@ public void force( } else { join.forceJoin(joiner, (Competitor) team); } - ChannelManager.broadcastAdminMessage( - translatable( - "join.ok.force.announce", - player(sender, NameStyle.FANCY), - joiner.getName(NameStyle.FANCY), - joiner.getParty().getName(), - oldParty.getName())); + ChannelManager.broadcastAdminMessage(translatable( + "join.ok.force.announce", + player(sender, NameStyle.FANCY), + joiner.getName(NameStyle.FANCY), + joiner.getParty().getName(), + oldParty.getName())); } @Command("shuffle") @@ -105,9 +104,8 @@ public void alias( final Component oldName = team.getName().color(NamedTextColor.GRAY); team.setName(name); - ChannelManager.broadcastAdminMessage( - translatable( - "match.alias.announce.ok", player(sender, NameStyle.FANCY), oldName, team.getName())); + ChannelManager.broadcastAdminMessage(translatable( + "match.alias.announce.ok", player(sender, NameStyle.FANCY), oldName, team.getName())); } @Command("scale ") @@ -123,12 +121,11 @@ public void scale( int maxSize = (int) (team.getMaxPlayers() * scale); team.setMaxSize(maxSize, maxOverfill); - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce.max", - player(sender, NameStyle.FANCY), - team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce.max", + player(sender, NameStyle.FANCY), + team.getName(), + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -148,12 +145,11 @@ public void max( else TextParser.assertInRange(maxOverfill, Range.atLeast(maxPlayers)); team.setMaxSize(maxPlayers, maxOverfill); - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce.max", - player(sender, NameStyle.FANCY), - team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce.max", + player(sender, NameStyle.FANCY), + team.getName(), + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -163,12 +159,11 @@ public void max( public void max(CommandSender sender, Match match, @Argument("teams") Collection teams) { for (Team team : teams) { team.resetMaxSize(); - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce.max", - player(sender, NameStyle.FANCY), - team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce.max", + player(sender, NameStyle.FANCY), + team.getName(), + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -183,12 +178,11 @@ public void min( TextParser.assertInRange(minPlayers, Range.atLeast(0)); for (Team team : teams) { team.setMinSize(minPlayers); - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce.min", - player(sender, NameStyle.FANCY), - team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce.min", + player(sender, NameStyle.FANCY), + team.getName(), + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } @@ -198,12 +192,11 @@ public void min( public void min(CommandSender sender, Match match, @Argument("teams") Collection teams) { for (Team team : teams) { team.resetMinSize(); - ChannelManager.broadcastAdminMessage( - translatable( - "match.resize.announce.min", - player(sender, NameStyle.FANCY), - team.getName(), - text(team.getMaxPlayers(), NamedTextColor.AQUA))); + ChannelManager.broadcastAdminMessage(translatable( + "match.resize.announce.min", + player(sender, NameStyle.FANCY), + team.getName(), + text(team.getMaxPlayers(), NamedTextColor.AQUA))); } } } diff --git a/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java b/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java index 92d9ed50e0..75e283defe 100644 --- a/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/TimeLimitCommand.java @@ -38,22 +38,20 @@ public void timelimit( @Argument("max-overtime") Duration maxOvertime, @Argument("end-overtime") Duration endOvertime) { time.cancel(); - time.setTimeLimit( - new TimeLimit( - null, - duration.isNegative() ? Duration.ZERO : duration, - overtime, - maxOvertime, - endOvertime, - result.orElse(null), - true)); + time.setTimeLimit(new TimeLimit( + null, + duration.isNegative() ? Duration.ZERO : duration, + overtime, + maxOvertime, + endOvertime, + result.orElse(null), + true)); time.start(); - ChannelManager.broadcastAdminMessage( - translatable( - "match.timeLimit.announce.commandOutput", - player(sender, NameStyle.FANCY), - clock(duration).color(NamedTextColor.AQUA), - result.map(r -> r.getDescription(match)).orElse(translatable("misc.unknown")))); + ChannelManager.broadcastAdminMessage(translatable( + "match.timeLimit.announce.commandOutput", + player(sender, NameStyle.FANCY), + clock(duration).color(NamedTextColor.AQUA), + result.map(r -> r.getDescription(match)).orElse(translatable("misc.unknown")))); } } diff --git a/core/src/main/java/tc/oc/pgm/command/VotingCommand.java b/core/src/main/java/tc/oc/pgm/command/VotingCommand.java index 6e83389306..de15022368 100644 --- a/core/src/main/java/tc/oc/pgm/command/VotingCommand.java +++ b/core/src/main/java/tc/oc/pgm/command/VotingCommand.java @@ -83,12 +83,11 @@ public void removeMap( @Argument("map") @Greedy MapInfo map) { VotePoolOptions vote = getVoteOptions(mapOrder); if (vote.removeMap(map)) { - ChannelManager.broadcastAdminMessage( - translatable( - "vote.remove", - NamedTextColor.GRAY, - UsernameFormatUtils.formatStaffName(sender, match), - map.getStyledName(MapNameStyle.COLOR))); + ChannelManager.broadcastAdminMessage(translatable( + "vote.remove", + NamedTextColor.GRAY, + UsernameFormatUtils.formatStaffName(sender, match), + map.getStyledName(MapNameStyle.COLOR))); } else { viewer.sendWarning(translatable("map.notFound")); } @@ -101,12 +100,11 @@ public void mode(CommandSender sender, MapOrder mapOrder, Match match) { VotePoolOptions vote = getVoteOptions(mapOrder); Component voteModeName = translatable( vote.toggleMode() ? "vote.mode.replace" : "vote.mode.create", NamedTextColor.LIGHT_PURPLE); - ChannelManager.broadcastAdminMessage( - translatable( - "vote.toggle", - NamedTextColor.GRAY, - UsernameFormatUtils.formatStaffName(sender, match), - voteModeName)); + ChannelManager.broadcastAdminMessage(translatable( + "vote.toggle", + NamedTextColor.GRAY, + UsernameFormatUtils.formatStaffName(sender, match), + voteModeName)); } @Command("clear") diff --git a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java index 0669e9ec8d..37e3f38606 100644 --- a/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java +++ b/core/src/main/java/tc/oc/pgm/command/util/PGMCommandGraph.java @@ -14,7 +14,6 @@ import tc.oc.pgm.action.actions.ExposedAction; import tc.oc.pgm.api.Config; import tc.oc.pgm.api.PGM; -import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.filter.Filter; import tc.oc.pgm.api.map.MapInfo; import tc.oc.pgm.api.map.MapLibrary; @@ -141,8 +140,7 @@ protected void registerCommands() { .handler(context -> minecraftHelp.queryCommands( context.optional("query").orElse(""), context.sender()))); - for (Channel channel : PGM.get().getChannelManager().getChannels()) - channel.registerCommand(manager); + PGM.get().getChannelManager().registerCommands(manager); } // Injectors diff --git a/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java b/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java index fa0bfa683d..7d6f500622 100644 --- a/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java +++ b/core/src/main/java/tc/oc/pgm/goals/TouchableGoal.java @@ -130,15 +130,14 @@ public void touch(final @Nullable ParticipantState toucher) { boolean firstForPlayer = touchingPlayers.add(toucher); boolean firstForPlayerLife = recentTouchingPlayers.add(toucher); - event = - new GoalTouchEvent( - this, - toucher.getParty(), - firstForCompetitor, - toucher, - firstForPlayer, - firstForPlayerLife, - getMatch().getTick().instant); + event = new GoalTouchEvent( + this, + toucher.getParty(), + firstForCompetitor, + toucher, + firstForPlayer, + firstForPlayerLife, + getMatch().getTick().instant); } getMatch().callEvent(event); diff --git a/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java b/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java index 6890991b76..ed35818a59 100644 --- a/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java +++ b/core/src/main/java/tc/oc/pgm/listeners/AntiGriefListener.java @@ -92,23 +92,21 @@ private void participantDefuse(Player player, Entity entity) { entity, translatable("moderation.defuse.player", NamedTextColor.RED, owner.getName())); - ChannelManager.broadcastAdminMessage( - translatable( - "moderation.defuse.alert.player", - NamedTextColor.GRAY, - clicker.getName(), - owner.getName(), - MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); + ChannelManager.broadcastAdminMessage(translatable( + "moderation.defuse.alert.player", + NamedTextColor.GRAY, + clicker.getName(), + owner.getName(), + MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); } else { this.notifyDefuse( clicker, entity, translatable("moderation.defuse.world", NamedTextColor.RED)); - ChannelManager.broadcastAdminMessage( - translatable( - "moderation.defuse.alert.world", - NamedTextColor.GRAY, - clicker.getName(), - MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); + ChannelManager.broadcastAdminMessage(translatable( + "moderation.defuse.alert.world", + NamedTextColor.GRAY, + clicker.getName(), + MinecraftComponent.entity(entity.getType()).color(NamedTextColor.DARK_RED))); } } } diff --git a/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java b/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java index 2918e9ec89..f2c6c1c024 100644 --- a/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java +++ b/core/src/main/java/tc/oc/pgm/listeners/ChatDispatcher.java @@ -30,12 +30,11 @@ public static ChatDispatcher get() { // does this a lot } - public static final TextComponent ADMIN_CHAT_PREFIX = - text() - .append(text("[", NamedTextColor.WHITE)) - .append(text("A", NamedTextColor.GOLD)) - .append(text("] ", NamedTextColor.WHITE)) - .build(); + public static final TextComponent ADMIN_CHAT_PREFIX = text() + .append(text("[", NamedTextColor.WHITE)) + .append(text("A", NamedTextColor.GOLD)) + .append(text("] ", NamedTextColor.WHITE)) + .build(); private static final Sound DM_SOUND = sound(key("random.orb"), Sound.Source.MASTER, 1f, 1.2f); diff --git a/util/src/main/java/tc/oc/pgm/util/bukkit/OnlinePlayerUUIDMapAdapter.java b/util/src/main/java/tc/oc/pgm/util/bukkit/OnlinePlayerUUIDMapAdapter.java new file mode 100644 index 0000000000..c6585c60de --- /dev/null +++ b/util/src/main/java/tc/oc/pgm/util/bukkit/OnlinePlayerUUIDMapAdapter.java @@ -0,0 +1,32 @@ +package tc.oc.pgm.util.bukkit; + +import java.util.Map; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; + +public class OnlinePlayerUUIDMapAdapter extends ListeningMapAdapter + implements Listener { + public OnlinePlayerUUIDMapAdapter(Plugin plugin) { + super(plugin); + } + + public OnlinePlayerUUIDMapAdapter(Map map, Plugin plugin) { + super(map, plugin); + } + + public boolean isValid(UUID key) { + Player player = Bukkit.getPlayer(key); + return player != null && player.isOnline(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerQuit(PlayerQuitEvent event) { + this.remove(event.getPlayer().getUniqueId()); + } +} From a3c9e3fa9f08cb44957e7b0aae5ee67c3c3e505e Mon Sep 17 00:00:00 2001 From: Pugzy Date: Wed, 6 Nov 2024 00:43:43 +0000 Subject: [PATCH 3/4] Logging changes --- .../java/tc/oc/pgm/api/channels/Channel.java | 6 ++ .../java/tc/oc/pgm/channels/AdminChannel.java | 5 ++ .../tc/oc/pgm/channels/ChannelManager.java | 67 ++++++++++++++----- .../pgm/channels/PrivateMessageChannel.java | 5 ++ .../java/tc/oc/pgm/channels/TeamChannel.java | 5 ++ 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java index 8bc4c65be7..556ba57343 100644 --- a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java +++ b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java @@ -16,6 +16,7 @@ import tc.oc.pgm.api.event.ChannelMessageEvent; import tc.oc.pgm.api.player.MatchPlayer; import tc.oc.pgm.api.setting.SettingValue; +import tc.oc.pgm.util.Audience; import tc.oc.pgm.util.Players; /** @@ -63,6 +64,10 @@ default SettingValue getSetting() { return null; } + default String getLoggerFormat(T target) { + return "<%s>: %s"; + } + /** * If the channel supports message redirection, where messages may be forwarded to another channel * or location e.g. team messages might be redirected to global chat post match end. @@ -206,5 +211,6 @@ default void broadcastMessage(Component component, T target, Predicate viewers = getBroadcastViewers(target); Component finalMessage = formatMessage(target, null, component); viewers.stream().filter(filter).forEach(player -> player.sendMessage(finalMessage)); + Audience.console().sendMessage(finalMessage); } } diff --git a/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java index 2a72c515a3..222f040126 100644 --- a/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/AdminChannel.java @@ -57,6 +57,11 @@ public SettingValue getSetting() { return SettingValue.CHAT_ADMIN; } + @Override + public String getLoggerFormat(Void target) { + return "[A] %s: %s"; + } + @Override public boolean canSendMessage(MatchPlayer sender) { return sender.getBukkit().hasPermission(Permissions.ADMINCHAT); diff --git a/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java index f4bfbd97b9..95590bfc6c 100644 --- a/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java +++ b/core/src/main/java/tc/oc/pgm/channels/ChannelManager.java @@ -14,11 +14,13 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -28,6 +30,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.incendo.cloud.CommandManager; import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.key.CloudKey; import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.channels.Channel; import tc.oc.pgm.api.event.ChannelMessageEvent; @@ -38,12 +41,16 @@ import tc.oc.pgm.api.setting.SettingValue; import tc.oc.pgm.api.setting.Settings; import tc.oc.pgm.ffa.Tribute; +import tc.oc.pgm.util.Audience; import tc.oc.pgm.util.Players; import tc.oc.pgm.util.bukkit.OnlinePlayerUUIDMapAdapter; import tc.oc.pgm.util.text.TextException; public class ChannelManager implements Listener { + CloudKey ORIGINAL_EVENT_KEY = + CloudKey.of("event", AsyncPlayerChatEvent.class); + private static final Cache CHAT_EVENT_CACHE = CacheBuilder.newBuilder() .weakKeys() @@ -85,14 +92,18 @@ public void registerCommands(CommandManager manager) { } } - public void processChat(MatchPlayer sender, String message) { - if (message.isEmpty()) return; + public boolean processChat(MatchPlayer sender, AsyncPlayerChatEvent event) { + final String message = event.getMessage().trim(); + if (message.isEmpty()) return false; CommandContext context = new CommandContext<>(sender.getBukkit(), manager); + context.store(ORIGINAL_EVENT_KEY, event); + Channel channel = shortcuts.get(message.charAt(0)); if (channel != null && channel.canSendMessage(sender)) { channel.processChatShortcut(sender, message, context); + context.optional(Channel.MESSAGE_KEY).ifPresent(event::setMessage); } if (channel == null) { @@ -100,15 +111,20 @@ public void processChat(MatchPlayer sender, String message) { channel.processChatMessage(sender, message, context); } - if (context.contains(Channel.MESSAGE_KEY)) process(channel, sender, context); + if (context.contains(Channel.MESSAGE_KEY)) { + return process(channel, sender, context); + } + + return false; } - public void process( + public boolean process( Channel channel, MatchPlayer sender, CommandContext context) { - processChannelMessage(calculateChannelRedirect(channel, sender, context), sender, context); + return processChannelMessage( + calculateChannelRedirect(channel, sender, context), sender, context); } - private void processChannelMessage( + private boolean processChannelMessage( Channel channel, MatchPlayer sender, CommandContext context) { if (!channel.canSendMessage(sender)) throw noPermission(); throwMuted(sender); @@ -124,7 +140,7 @@ private void processChannelMessage( CHAT_EVENT_CACHE.put(asyncEvent, true); sender.getMatch().callEvent(asyncEvent); - if (asyncEvent.isCancelled()) return; + if (asyncEvent.isCancelled()) return false; final ChannelMessageEvent event = new ChannelMessageEvent<>(channel, sender, target, viewers, asyncEvent.getMessage()); @@ -135,7 +151,7 @@ private void processChannelMessage( if (event.getSender() != null && event.getCancellationReason() != null) { event.getSender().sendWarning(event.getCancellationReason()); } - return; + return false; } Component finalMessage = event @@ -144,6 +160,15 @@ private void processChannelMessage( event.getViewers().forEach(player -> player.sendMessage(finalMessage)); channel.messageSent(event); + + String logFormat = "[CHAT] " + channel.getLoggerFormat(target); + context.optional(ORIGINAL_EVENT_KEY).ifPresentOrElse(e -> e.setFormat(logFormat), () -> { + String message = String.format( + logFormat, sender.getBukkit().getDisplayName(), context.get(Channel.MESSAGE_KEY)); + Audience.console().sendMessage(text(message)); + }); + + return true; } private Channel calculateChannelRedirect( @@ -208,17 +233,29 @@ public void onChat(AsyncPlayerChatEvent event) { return; } - event.setCancelled(true); + event.getRecipients().clear(); final MatchPlayer player = PGM.get().getMatchManager().getPlayer(event.getPlayer()); if (player == null) return; - final String message = event.getMessage().trim(); - try { - processChat(player, message); - } catch (TextException e) { - // Allow sub-handlers to throw command exceptions just fine - player.sendWarning(e); + Runnable completion = () -> { + try { + boolean sent = processChat(player, event); + if (!sent) event.setCancelled(true); + } catch (TextException e) { + // Allow sub-handlers to throw command exceptions just fine + event.setCancelled(true); + player.sendWarning(e); + } + }; + + if (Bukkit.isPrimaryThread()) completion.run(); + else { + try { + PGM.get().getExecutor().submit(completion).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } } diff --git a/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java b/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java index 949c487174..706432441f 100644 --- a/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/PrivateMessageChannel.java @@ -68,6 +68,11 @@ public Character getShortcut() { return '@'; } + @Override + public String getLoggerFormat(MatchPlayer target) { + return "(DM) %s -> " + target.getBukkit().getDisplayName() + ": %s"; + } + @Override public MatchPlayer getTarget(MatchPlayer sender, CommandContext arguments) { MatchPlayer target = arguments.get(TARGET_KEY); diff --git a/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java index 0955da0e89..7bd47d7548 100644 --- a/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java +++ b/core/src/main/java/tc/oc/pgm/channels/TeamChannel.java @@ -37,6 +37,11 @@ public SettingValue getSetting() { return SettingValue.CHAT_TEAM; } + @Override + public String getLoggerFormat(Party target) { + return "(" + target.getNameLegacy() + ") %s: %s"; + } + @Override public boolean supportsRedirect() { return true; From 51a656424332a27966f90a246c1879b05a8f0c0e Mon Sep 17 00:00:00 2001 From: Pugzy Date: Wed, 6 Nov 2024 01:20:27 +0000 Subject: [PATCH 4/4] Update documentation --- core/src/main/java/tc/oc/pgm/api/channels/Channel.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java index 556ba57343..bc39c41b34 100644 --- a/core/src/main/java/tc/oc/pgm/api/channels/Channel.java +++ b/core/src/main/java/tc/oc/pgm/api/channels/Channel.java @@ -64,6 +64,13 @@ default SettingValue getSetting() { return null; } + /** + * Retrieves the format these channel messages should be logged using via the + * {@code AsyncPlayerChatEvent}. + * + * @param target the message target + * @return formatted messaged printed to console + */ default String getLoggerFormat(T target) { return "<%s>: %s"; }