diff --git a/src/main/java/me/crypnotic/neutron/api/command/CommandWrapper.java b/src/main/java/me/crypnotic/neutron/api/command/CommandWrapper.java index 7a46365..520dd8a 100644 --- a/src/main/java/me/crypnotic/neutron/api/command/CommandWrapper.java +++ b/src/main/java/me/crypnotic/neutron/api/command/CommandWrapper.java @@ -78,6 +78,11 @@ public void assertNotNull(CommandSource source, Object value, LocaleMessage mess assertCustom(source, value != null, message, values); } + @SneakyThrows + public void assertNotIgnoring(CommandSource source, CommandSource ignoreSource, Player target, LocaleMessage message, Object... values) { + getUser(ignoreSource).ifPresent(u -> assertCustom(source, !u.isIgnoringPlayer(target), message, values)); + } + @SneakyThrows public void assertPermission(CommandSource source, String permission) { assertCustom(source, source.hasPermission(permission), LocaleMessage.NO_PERMISSION); diff --git a/src/main/java/me/crypnotic/neutron/api/locale/LocaleMessage.java b/src/main/java/me/crypnotic/neutron/api/locale/LocaleMessage.java index a579756..d54e0c0 100644 --- a/src/main/java/me/crypnotic/neutron/api/locale/LocaleMessage.java +++ b/src/main/java/me/crypnotic/neutron/api/locale/LocaleMessage.java @@ -37,6 +37,14 @@ public enum LocaleMessage { FIND_MESSAGE("&b{0} &7is connected to &b{1}"), + IGNORE_AMBIGUOUS_PLAYER("&cPlayer '{0}' is ambiguous; did you mean: {1}"), + IGNORE_LIST_EMPTY("&aYou are not ignoring anyone."), + IGNORE_LIST_HEAD("&aYou are ignoring the following players:\n"), + IGNORE_LIST_ITEM("&f{0}&7, "), + IGNORE_LIST_ITEM_UNKNOWN("&f&ounknown&7, "), + IGNORE_NOW_IGNORING("&aYou are now ignoring {0}."), + IGNORE_NOW_NOT_IGNORING("&aYou are no longer ignoring {0}."), + INFO_HEADER("&l&7==> Information for player: &b{0}"), INFO_LOCALE("&7Locale: &b{0}"), INFO_PING("&7Ping: &b{0}"), @@ -49,6 +57,8 @@ public enum LocaleMessage { LIST_HEADER("&aThere are currently &b{0} &aplayers online\n&7&oHover over a server to see the players online"), LIST_MESSAGE("&a[{0}] &e{1} player{2} online"), + MESSAGE_IGNORED_BY_TARGET("&cYou can't message {0} right now."), + MESSAGE_IGNORING_TARGET("&cYou can't message {0} because you are ignoring them."), MESSAGE_SENDER("&b&lme \u00bb {0} &7> &o"), MESSAGE_RECEIVER("&b&l{0} \u00bb me &7> &o"), diff --git a/src/main/java/me/crypnotic/neutron/api/user/User.java b/src/main/java/me/crypnotic/neutron/api/user/User.java index 1db8c89..a60e7e8 100644 --- a/src/main/java/me/crypnotic/neutron/api/user/User.java +++ b/src/main/java/me/crypnotic/neutron/api/user/User.java @@ -1,6 +1,7 @@ package me.crypnotic.neutron.api.user; import java.util.Optional; +import java.util.Set; import java.util.UUID; import com.velocitypowered.api.command.CommandSource; @@ -17,11 +18,19 @@ public interface User { String getName(); CommandSource getReplyRecipient(); + + Set getIgnoredPlayers(); Optional getUUID(); void setReplyRecipient(CommandSource source); + void setIgnoringPlayer(Player target, boolean ignore); + + default boolean isIgnoringPlayer(Player target) { + return getIgnoredPlayers().contains(target.getUniqueId()); + } + default boolean isPlayer() { return getBase().isPresent() && getBase().get() instanceof Player; } diff --git a/src/main/java/me/crypnotic/neutron/manager/user/holder/ConsoleUser.java b/src/main/java/me/crypnotic/neutron/manager/user/holder/ConsoleUser.java index d82b3b6..9a255f6 100644 --- a/src/main/java/me/crypnotic/neutron/manager/user/holder/ConsoleUser.java +++ b/src/main/java/me/crypnotic/neutron/manager/user/holder/ConsoleUser.java @@ -1,6 +1,8 @@ package me.crypnotic.neutron.manager.user.holder; +import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import com.velocitypowered.api.command.CommandSource; @@ -42,4 +44,19 @@ public void save() throws Exception { public Optional getUUID() { return Optional.empty(); } + + @Override + public void setIgnoringPlayer(CommandSource source) { + /* noop */ + } + + @Override + public Set getIgnoredPlayers() { + return Collections.emptySet(); + } + + @Override + public boolean isIgnoringPlayer(CommandSource source) { + return false; + } } diff --git a/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerData.java b/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerData.java index 2dec920..a0afb4b 100644 --- a/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerData.java +++ b/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerData.java @@ -1,11 +1,17 @@ package me.crypnotic.neutron.manager.user.holder; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import com.velocitypowered.api.command.CommandSource; import lombok.Data; import ninja.leaping.configurate.objectmapping.Setting; import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; @ConfigSerializable @Data @@ -19,6 +25,17 @@ class PlayerData { @Setting(comment = "The player's last known username.") private String username; + @Setting(comment = "Players that this player is ignoring") + private List ignoredPlayers = Collections.emptyList(); // Configurate includes a List TypeSerializer, so let's use that. + + public Set getIgnoredPlayers() { + return Sets.newHashSet(ignoredPlayers); + } + + public void setIgnoredPlayers(Set ignoredPlayers) { + this.ignoredPlayers = Lists.newArrayList(ignoredPlayers); + } + // Non-persisted data - this is not saved when the user is unloaded. private WeakReference replyRecipient = null; diff --git a/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerUser.java b/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerUser.java index c09fc55..d3eb5ad 100644 --- a/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerUser.java +++ b/src/main/java/me/crypnotic/neutron/manager/user/holder/PlayerUser.java @@ -3,9 +3,12 @@ import static me.crypnotic.neutron.api.Neutron.getNeutron; import java.lang.ref.WeakReference; +import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import com.google.common.collect.Sets; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; @@ -67,7 +70,25 @@ public CommandSource getReplyRecipient() { return data.getReplyRecipient(); } + @Override + public Set getIgnoredPlayers() { + return Collections.unmodifiableSet(data.getIgnoredPlayers()); + } + public void setReplyRecipient(CommandSource source) { data.setReplyRecipient(source); } + + @Override + public void setIgnoringPlayer(Player target, boolean ignore) { + Set newSet = Sets.newHashSet(data.getIgnoredPlayers()); + + if (ignore) { + newSet.add(target.getUniqueId()); + } else { + newSet.remove(target.getUniqueId()); + } + + data.setIgnoredPlayers(Collections.unmodifiableSet(newSet)); + } } diff --git a/src/main/java/me/crypnotic/neutron/module/command/Commands.java b/src/main/java/me/crypnotic/neutron/module/command/Commands.java index 69d369c..3a0339d 100644 --- a/src/main/java/me/crypnotic/neutron/module/command/Commands.java +++ b/src/main/java/me/crypnotic/neutron/module/command/Commands.java @@ -35,6 +35,7 @@ public enum Commands { ALERT("alert", AlertCommand::new), FIND("find", FindCommand::new), + IGNORE("ignore", IgnoreCommand::new), INFO("info", InfoCommand::new), GLIST("glist", GlistCommand::new), MESSAGE("message", MessageCommand::new), diff --git a/src/main/java/me/crypnotic/neutron/module/command/options/IgnoreCommand.java b/src/main/java/me/crypnotic/neutron/module/command/options/IgnoreCommand.java new file mode 100644 index 0000000..8b73c81 --- /dev/null +++ b/src/main/java/me/crypnotic/neutron/module/command/options/IgnoreCommand.java @@ -0,0 +1,81 @@ +package me.crypnotic.neutron.module.command.options; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import me.crypnotic.neutron.api.command.CommandContext; +import me.crypnotic.neutron.api.command.CommandWrapper; +import me.crypnotic.neutron.api.locale.LocaleMessage; +import me.crypnotic.neutron.api.user.User; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.HoverEvent; + +import java.util.*; +import java.util.stream.Collectors; + +public class IgnoreCommand extends CommandWrapper { + @Override + public void handle(CommandSource source, CommandContext context) throws CommandExitException { + assertPermission(source, "neutron.command.ignore"); + assertPlayer(source, LocaleMessage.PLAYER_ONLY_COMMAND); + + if (context.size() == 0) { + handleList(source); + } else { + handleToggle(source, context); + } + } + + private void handleList(CommandSource source) throws CommandExitException { + User user = getUser(source).get(); + + if (user.getIgnoredPlayers().isEmpty()) { + message(source, LocaleMessage.IGNORE_LIST_EMPTY); + return; + } + + Component message = getMessage(source, LocaleMessage.IGNORE_LIST_HEAD); + + for (UUID uuid : user.getIgnoredPlayers()) { + Optional> optUser = getNeutron().getUserManager().getUser(uuid); + + if (optUser.isPresent()) { + User ignored = optUser.get(); + message = message.append(getMessage(source, LocaleMessage.IGNORE_LIST_ITEM, ignored.getName())); + } else { + message = message.append( + getMessage(source, LocaleMessage.IGNORE_LIST_ITEM_UNKNOWN, uuid.toString()) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of(uuid.toString())))); + } + + source.sendMessage(message); + } + + } + + private void handleToggle(CommandSource source, CommandContext context) throws CommandExitException { + Collection matches = getNeutron().getProxy().matchPlayer(context.get(0)); + assertCustom(source, matches.size() != 0, LocaleMessage.UNKNOWN_PLAYER); + assertCustom(source, matches.size() < 2, LocaleMessage.IGNORE_AMBIGUOUS_PLAYER); + Player target = matches.stream().findFirst().get(); + + User user = getUser(source).get(); + + boolean newState = !user.isIgnoringPlayer(target); + + if (context.size() > 1) { + newState = Boolean.parseBoolean(context.get(1)); + } + + user.setIgnoringPlayer(target, newState); + + message(source, + user.isIgnoringPlayer(target) ? LocaleMessage.IGNORE_NOW_IGNORING : LocaleMessage.IGNORE_NOW_NOT_IGNORING, + target.getUsername()); + } + + @Override + public String getUsage() { + return "/ignore [player]"; + } +} diff --git a/src/main/java/me/crypnotic/neutron/module/command/options/MessageCommand.java b/src/main/java/me/crypnotic/neutron/module/command/options/MessageCommand.java index a993255..5ed1fc6 100644 --- a/src/main/java/me/crypnotic/neutron/module/command/options/MessageCommand.java +++ b/src/main/java/me/crypnotic/neutron/module/command/options/MessageCommand.java @@ -59,6 +59,14 @@ public void handle(CommandSource source, CommandContext context) throws CommandE final Optional> sender = getUser(source); final Optional> recipient = getUser(target); + // Ensure source is not ignoring target + assertNotIgnoring(source, source, target, LocaleMessage.MESSAGE_IGNORING_TARGET, target.getUsername()); + + // Ensure target is not ignoring source + if (source instanceof Player && !source.hasPermission("neutron.command.message.ignore.bypass")) { + assertNotIgnoring(source, target, (Player) source, LocaleMessage.MESSAGE_IGNORED_BY_TARGET, target.getUsername()); + } + UserPrivateMessageEvent event = new UserPrivateMessageEvent(sender, recipient, content, false); getNeutron().getProxy().getEventManager().fire(event).thenAccept(resultEvent -> { diff --git a/src/main/java/me/crypnotic/neutron/module/command/options/ReplyCommand.java b/src/main/java/me/crypnotic/neutron/module/command/options/ReplyCommand.java index ed79635..b2cfce2 100644 --- a/src/main/java/me/crypnotic/neutron/module/command/options/ReplyCommand.java +++ b/src/main/java/me/crypnotic/neutron/module/command/options/ReplyCommand.java @@ -60,6 +60,16 @@ public void handle(CommandSource source, CommandContext context) throws CommandE final Optional> sender = getUser(source); final Optional> recipient = getUser(target); + // Ensure source is not ignoring target + if (target instanceof Player) { + assertNotIgnoring(source, source, (Player) target, LocaleMessage.MESSAGE_IGNORING_TARGET, targetName); + } + + // Ensure target is not ignoring source + if (source instanceof Player && !source.hasPermission("neutron.command.message.ignore.bypass")) { + assertNotIgnoring(source, target, (Player) source, LocaleMessage.MESSAGE_IGNORED_BY_TARGET, targetName); + } + UserPrivateMessageEvent event = new UserPrivateMessageEvent(sender, recipient, content, true); getNeutron().getProxy().getEventManager().fire(event).thenAccept(resultEvent -> { diff --git a/src/main/resources/config.conf b/src/main/resources/config.conf index 017d510..831ee65 100644 --- a/src/main/resources/config.conf +++ b/src/main/resources/config.conf @@ -15,6 +15,11 @@ command { enabled = true aliases = ["find"] } + + ignore { + enabled = true + aliases = ["ignore", "block"] + } info { enabled = true diff --git a/src/main/resources/locales/en_US.conf b/src/main/resources/locales/en_US.conf index 6453a19..6d53676 100644 --- a/src/main/resources/locales/en_US.conf +++ b/src/main/resources/locales/en_US.conf @@ -5,6 +5,14 @@ connect_quit_message = "&b{0} &7left the network" find_message = "&b{0} &7is connected to &b{1}" +ignore_ambiguous_player = "&cplayer '{0}' is ambiguous; did you mean: {1}" +ignore_list_empty = "&aYou are not ignoring anyone." +ignore_list_head = "&aYou are ignoring the following players:\n" +ignore_list_item = "&f{0}&7, " +ignore_list_item_unknown = "&f&ounknown&7, " +ignore_now_ignoring = "&aYou are now ignoring {0}." +ignore_now_not_ignoring = "&aYou are no longer ignoring {0}." + info_header = "&l&7==> Information for player = &b{0}" info_locale = "&7Locale = &b{0}" info_ping = "&7Ping = &b{0}" @@ -17,6 +25,8 @@ invalid_usage = "&cUsage = {0}" list_header = "&aThere are currently &b{0} &aplayers online\n&7&oHover over a server to see the players online" list_message = "&a[{0}] &e{1} online" +message_ignored_by_target = "&cYou can't message {0} right now." +message_ignoring_target = "&cYou can't message {0} because you are ignoring them." message_sender = "&b&lme » {0} &7> &o" message_receiver = "&b&l{0} » me &7> &o" @@ -35,4 +45,4 @@ player_only_command = "&cOnly players can use this command." player_only_subcommand = "&cOnly players can use this subcommand." unknown_player = "&cUnknown player = {0}" -unknown_server = "&cUnknown server = {0}" \ No newline at end of file +unknown_server = "&cUnknown server = {0}"