diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/Sonar.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/Sonar.java index bc3ae0e07..b2325e26d 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/Sonar.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/Sonar.java @@ -24,7 +24,6 @@ import xyz.jonesdev.sonar.api.event.SonarEventManager; import xyz.jonesdev.sonar.api.fallback.Fallback; import xyz.jonesdev.sonar.api.logger.LoggerWrapper; -import xyz.jonesdev.sonar.api.server.ServerWrapper; import xyz.jonesdev.sonar.api.verbose.Verbose; import java.io.File; @@ -35,9 +34,9 @@ public interface Sonar { String LINE_SEPARATOR = "\n"; // Using System.lineSeparator is broken, for some reason... /** - * @return A small wrapper for the server + * @return The platform the plugin is being run on */ - @NotNull ServerWrapper getServer(); + @NotNull SonarPlatform getPlatform(); /** * @return A small wrapper for the plugin logger so we can use the logger everywhere diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/CommandInvocation.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/CommandInvocation.java index f2939310b..fb7374470 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/CommandInvocation.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/CommandInvocation.java @@ -19,12 +19,14 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; @Getter @RequiredArgsConstructor +@ToString(of = "rawArguments") public final class CommandInvocation { private final InvocationSource sender; - private final Subcommand command; + private final Subcommand subcommand; private final String[] rawArguments; } diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java index da2db1dd1..71e765893 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java @@ -28,6 +28,7 @@ public abstract class InvocationSource { private final String name; private final Audience audience; + private final boolean player; /** * Sends an empty chat message to the command executor diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java index ae448e205..64a8f4ef5 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java @@ -18,14 +18,18 @@ package xyz.jonesdev.sonar.api.command; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.jetbrains.annotations.NotNull; import xyz.jonesdev.cappuccino.Cappuccino; import xyz.jonesdev.cappuccino.ExpiringCache; import xyz.jonesdev.sonar.api.Sonar; +import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; +import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument; import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; public interface SonarCommand { List TAB_SUGGESTIONS = new ArrayList<>(); @@ -34,63 +38,55 @@ public interface SonarCommand { ExpiringCache DELAY = Cappuccino.buildExpiring(500L); - int COPYRIGHT_YEAR = Calendar.getInstance().get(Calendar.YEAR); - - List CACHED_HELP_MESSAGE = new Vector<>(); + List CACHED_HELP_MESSAGE = new ArrayList<>(); - default void cacheHelpMessage() { - CACHED_HELP_MESSAGE.addAll(Arrays.asList( - Component.text("Running Sonar " + Sonar.get().getVersion() - + " on " + Sonar.get().getServer().getPlatform().getDisplayName() - + ".", NamedTextColor.YELLOW), - Component.text("(C) " + COPYRIGHT_YEAR + " Jones Development and Sonar Contributors", NamedTextColor.YELLOW), - Component.text("https://github.com/jonesdevelopment/sonar", NamedTextColor.GREEN) - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar")), - Component.empty(), - Component.text("Need help or have any questions?", NamedTextColor.YELLOW), - Component.textOfChildren( - Component.text("Open a ticket on the Discord ", NamedTextColor.YELLOW) - .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open Discord)"))) - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://jonesdev.xyz/discord/")), - Component.text("or open a new issue on GitHub.", NamedTextColor.YELLOW) - .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, Component.text("(Click to open GitHub)"))) - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/jonesdevelopment/sonar" + - "/issues")) - ), - Component.empty() - )); + static void prepareCachedMessages() { + // Cache help message + CACHED_HELP_MESSAGE.clear(); + for (final String message : Sonar.get().getConfig().getCommands().getHelpHeader()) { + CACHED_HELP_MESSAGE.add(MiniMessage.miniMessage().deserialize(message + .replace("%version%", Sonar.get().getVersion().getFormatted()) + .replace("%platform%", Sonar.get().getPlatform().getDisplayName()) + .replace("%copyright_year%", String.valueOf(Calendar.getInstance().get(Calendar.YEAR))))); + } - Sonar.get().getSubcommandRegistry().getSubcommands().forEach(sub -> { - Component component = Component.textOfChildren( - Component.text(" ▪ ", NamedTextColor.GRAY), - Component.text("/sonar " + sub.getInfo().name(), NamedTextColor.GREEN), - Component.text(" - ", NamedTextColor.GRAY), - Component.text(sub.getInfo().description(), NamedTextColor.WHITE) - ); + final String subcommandFormat = Sonar.get().getConfig().getCommands().getHelpSubcommands(); + Sonar.get().getSubcommandRegistry().getSubcommands().forEach(subcommand -> { + final Component deserialized = MiniMessage.miniMessage().deserialize(subcommandFormat + .replace("%subcommand%", subcommand.getInfo().name()) + .replace("%description%", subcommand.getInfo().description()) + .replace("%only_players%", subcommand.getInfo().onlyPlayers() ? "" : "") + .replace("%require_console%", subcommand.getInfo().onlyConsole() ? "" : "") + .replace("%permission%", subcommand.getPermission()) + .replace("%aliases%", subcommand.getAliases())); + CACHED_HELP_MESSAGE.add(deserialized); + }); - Component hoverComponent = Component.textOfChildren( - Component.text("Only players: ", NamedTextColor.GRAY), - Component.text(sub.getInfo().onlyPlayers() ? "✔" : "✗", - sub.getInfo().onlyPlayers() ? NamedTextColor.GREEN : NamedTextColor.RED), - Component.newline(), - Component.text("Require console: ", NamedTextColor.GRAY), - Component.text(sub.getInfo().onlyConsole() ? "✔" : "✗", - sub.getInfo().onlyConsole() ? NamedTextColor.GREEN : NamedTextColor.RED), - Component.newline(), - Component.text("Permission: ", NamedTextColor.GRAY), - Component.text(sub.getPermission(), NamedTextColor.WHITE) - ); - if (sub.getInfo().aliases().length > 0) { - hoverComponent = hoverComponent - .append(Component.newline()) - .append(Component.text("Aliases: ", NamedTextColor.GRAY)) - .append(Component.text(sub.getAliases(), NamedTextColor.WHITE)); + // Don't re-cache tab suggestions + if (!TAB_SUGGESTIONS.isEmpty()) return; + // Cache tab suggestions + for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { + TAB_SUGGESTIONS.add(subcommand.getInfo().name()); + if (subcommand.getInfo().aliases().length > 0) { + TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases())); } - component = component - .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, - "/sonar " + sub.getInfo().name() + " ")) - .hoverEvent(HoverEvent.hoverEvent(HoverEvent.Action.SHOW_TEXT, hoverComponent)); - CACHED_HELP_MESSAGE.add(component); - }); + final List parsedArguments = Arrays.stream(subcommand.getInfo().arguments()) + .map(Argument::value) + .collect(Collectors.toList()); + ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), parsedArguments); + for (final String alias : subcommand.getInfo().aliases()) { + ARG_TAB_SUGGESTIONS.put(alias, parsedArguments); + } + } + } + + default List getCachedTabSuggestions(final String @NotNull [] arguments) { + if (arguments.length <= 1) { + return TAB_SUGGESTIONS; + } else if (arguments.length == 2) { + final String subCommandName = arguments[0].toLowerCase(); + return ARG_TAB_SUGGESTIONS.getOrDefault(subCommandName, emptyList()); + } + return emptyList(); } } diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/subcommand/Subcommand.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/subcommand/Subcommand.java index d33427fbb..3a62cecda 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/subcommand/Subcommand.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/subcommand/Subcommand.java @@ -83,5 +83,31 @@ protected final void incorrectUsage(final @NotNull InvocationSource sender) { .replace("%usage%", info.name() + " (" + arguments + ")")); } - public abstract void execute(final @NotNull CommandInvocation invocation); + public final void invoke(final @NotNull InvocationSource invocationSource, final String @NotNull [] arguments) { + // Check if the subcommand can only be executed by players + if (getInfo().onlyPlayers() && !invocationSource.isPlayer()) { + invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly()); + return; + } + + // Check if the subcommand can only be executed though console + if (getInfo().onlyConsole() && invocationSource.isPlayer()) { + invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly()); + return; + } + + final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, this, arguments); + + // The subcommands has arguments which are not present in the executed command + if (getInfo().argumentsRequired() && getInfo().arguments().length > 0 && arguments.length <= 1) { + invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage() + .replace("%usage%", getInfo().name() + " (" + getArguments() + ")")); + return; + } + + // Execute the sub command + execute(commandInvocation); + } + + protected abstract void execute(final @NotNull CommandInvocation invocation); } diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SimpleYamlConfig.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SimpleYamlConfig.java index 3e792e42a..fb8c728e8 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SimpleYamlConfig.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SimpleYamlConfig.java @@ -18,6 +18,7 @@ package xyz.jonesdev.sonar.api.config; import lombok.Getter; +import org.jetbrains.annotations.NotNull; import org.simpleyaml.configuration.comments.format.YamlCommentFormat; import org.simpleyaml.configuration.file.YamlFile; import xyz.jonesdev.sonar.api.Sonar; @@ -36,8 +37,8 @@ public final class SimpleYamlConfig { private static final List HEADER = Arrays.asList( "", - "Running Sonar version " + Sonar.get().getVersion() - + " on " + Sonar.get().getServer().getPlatform().getDisplayName(), + String.format("Running Sonar version %s on %s", + Sonar.get().getVersion(), Sonar.get().getPlatform().getDisplayName()), "Need help or have questions? https://jonesdev.xyz/discord", "https://github.com/jonesdevelopment/sonar", "" @@ -47,7 +48,7 @@ public SimpleYamlConfig(final File dataFolder, final String fileName) { this(new File(dataFolder, fileName + ".yml"), dataFolder); } - private SimpleYamlConfig(final File file, final File folder) { + private SimpleYamlConfig(final File file, final @NotNull File folder) { if (!folder.exists() && !folder.mkdir()) { throw new IllegalStateException("Could not create folder?!"); } diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SonarConfiguration.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SonarConfiguration.java index 89d840115..50ee0bbaf 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SonarConfiguration.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/config/SonarConfiguration.java @@ -22,6 +22,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.Sonar; +import xyz.jonesdev.sonar.api.command.SonarCommand; import xyz.jonesdev.sonar.api.dependencies.Dependency; import java.io.File; @@ -157,10 +158,11 @@ public static final class Commands { private String networkStatistics; private String cpuStatistics; + private List helpHeader; + private String helpSubcommands; + private String verboseSubscribed; private String verboseUnsubscribed; - private String verboseSubscribedOther; - private String verboseUnsubscribedOther; private String reloading; private String reloaded; @@ -507,6 +509,33 @@ public void load() { "%footer%" )))); + messagesConfig.getYaml().setComment("commands.main", + "Translations for '/sonar'"); + messagesConfig.getYaml().setComment("commands.main.header", + "Informational message that is shown above everything when running the main command"); + commands.helpHeader = messagesConfig.getStringList("commands.main.header", + Arrays.asList( + "Running Sonar %version% on %platform%.", + "(C) %copyright_year% Jones Development and Sonar Contributors", + "https://github.com/jonesdevelopment/sonar", + "", + "Need help or have any questions?", + "Open a " + + "ticket on the Discord or open a new issue on " + + "GitHub.", + "" + )); + messagesConfig.getYaml().setComment("commands.main.subcommands", + "Formatting of the list of subcommands shown when running the main command"); + commands.helpSubcommands = formatString(messagesConfig.getString("commands.main.subcommands", + "Only players: " + + "%only_players%
Require console: %require_console%
Permission: " + + "%permission%
Aliases: %aliases%'>/sonar " + + "%subcommand% - %description%")); + + SonarCommand.prepareCachedMessages(); + messagesConfig.getYaml().setComment("commands.reload", "Translations for '/sonar reload'"); messagesConfig.getYaml().setComment("commands.reload.start", @@ -543,16 +572,6 @@ public void load() { commands.verboseUnsubscribed = formatString(messagesConfig.getString("commands.verbose.unsubscribed", "%prefix%You are no longer viewing Sonar verbose.")); - messagesConfig.getYaml().setComment("commands.verbose.subscribed-other", - "Message that is shown when a player makes another player subscribe to Sonar verbose"); - commands.verboseSubscribedOther = formatString(messagesConfig.getString("commands.verbose.subscribed-other", - "%prefix%%player% is now viewing Sonar verbose.")); - - messagesConfig.getYaml().setComment("commands.verbose.unsubscribed-other", - "Message that is shown when a player makes another player unsubscribe from Sonar verbose"); - commands.verboseUnsubscribedOther = formatString(messagesConfig.getString("commands.verbose.unsubscribed-other", - "%prefix%%player% is no longer viewing Sonar verbose.")); - messagesConfig.getYaml().setComment("commands.blacklist", "Translations for '/sonar blacklist'"); messagesConfig.getYaml().setComment("commands.blacklist.empty", diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/server/ServerWrapper.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/server/ServerWrapper.java deleted file mode 100644 index 3e3a63400..000000000 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/server/ServerWrapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2023 Sonar Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package xyz.jonesdev.sonar.api.server; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import xyz.jonesdev.sonar.api.SonarPlatform; -import xyz.jonesdev.sonar.api.command.InvocationSource; - -import java.util.Optional; - -@Getter -@RequiredArgsConstructor -public abstract class ServerWrapper { - - /** - * Platform of the server (Velocity, BungeeCord, Spigot) - */ - private final SonarPlatform platform; - - /** - * @param username Username of the player - * @return Optional player wrapped as InvocationSender - * @see InvocationSource - */ - public abstract Optional getOnlinePlayer(final String username); -} diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/verbose/Verbose.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/verbose/Verbose.java index 0d2a57207..3e0eb6ca7 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/verbose/Verbose.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/verbose/Verbose.java @@ -19,6 +19,7 @@ import lombok.Getter; import lombok.val; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.jetbrains.annotations.NotNull; @@ -28,21 +29,24 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer; import java.util.Collection; +import java.util.Map; import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT; import static xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter.INCOMING; import static xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter.OUTGOING; @Getter -public abstract class Verbose implements JVMProfiler { - protected final @NotNull Collection subscribers = new Vector<>(0); +public final class Verbose implements JVMProfiler { + private final @NotNull Collection subscribers = new Vector<>(0); + private final @NotNull Map audiences = new ConcurrentHashMap<>(); private final SystemTimer secondTimer = new SystemTimer(); - protected int joinsPerSecond, totalJoins; + private int joinsPerSecond, totalJoins; private int lastTotalJoins, animationIndex; // Run action bar verbose - public final void update() { + public void update() { // Clean up all blacklisted IPs Sonar.get().getFallback().getBlacklisted().cleanUp(false); @@ -55,11 +59,19 @@ public final void update() { lastTotalJoins = totalJoins; } + // Don't prepare component if there are no subscribers + if (subscribers.isEmpty()) return; + // Prepare the action bar format component + final Component component = prepareActionBarFormat(); // Send the action bar to all online players - broadcast(prepareActionBarFormat()); + for (final String subscriber : subscribers) { + final Audience audience = audiences.get(subscriber); + if (audience == null) continue; + audience.sendActionBar(component); + } } - protected @NotNull Component prepareActionBarFormat() { + public @NotNull Component prepareActionBarFormat() { return MiniMessage.miniMessage().deserialize(Sonar.get().getConfig().getVerbose().getActionBarLayout() .replace("%queued%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size())) @@ -83,34 +95,31 @@ public final void update() { .replace("%animation%", nextAnimation())); } - protected final String nextAnimation() { + public String nextAnimation() { val animations = Sonar.get().getConfig().getVerbose().getAnimation(); final int nextIndex = ++animationIndex % animations.size(); return animations.toArray(new String[0])[nextIndex]; } - // Run action bar verbose - protected abstract void broadcast(final Component component); - /** - * @param subscriber Name of the player who subscribed - * @return Whether the player is subscribed or not + * @param name Name of the audience + * @return Whether the audience is subscribed or not */ - public final boolean isSubscribed(final @NotNull String subscriber) { - return subscribers.contains(subscriber); + public boolean isSubscribed(final @NotNull String name) { + return subscribers.contains(name); } /** - * @param subscriber Name of the player to subscribe + * @param name Name of the audience to subscribe */ - public final void subscribe(final @NotNull String subscriber) { - subscribers.add(subscriber); + public void subscribe(final String name) { + subscribers.add(name); } /** - * @param subscriber Name of the player to unsubscribe + * @param name Name of the audience to unsubscribe */ - public final void unsubscribe(final @NotNull String subscriber) { - subscribers.remove(subscriber); + public void unsubscribe(final String name) { + subscribers.remove(name); } } diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/SonarBukkit.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/SonarBukkit.java index c09137f64..70e3f00ac 100644 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/SonarBukkit.java +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/SonarBukkit.java @@ -18,20 +18,17 @@ package xyz.jonesdev.sonar.bukkit; import lombok.Getter; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bstats.bukkit.Metrics; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.SonarPlatform; -import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter; import xyz.jonesdev.sonar.api.logger.LoggerWrapper; -import xyz.jonesdev.sonar.api.server.ServerWrapper; -import xyz.jonesdev.sonar.bukkit.command.BukkitInvocationSource; +import xyz.jonesdev.sonar.bukkit.audience.AudienceListener; import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand; -import xyz.jonesdev.sonar.bukkit.verbose.ActionBarVerbose; import xyz.jonesdev.sonar.common.boot.SonarBootstrap; import java.util.Objects; -import java.util.Optional; import java.util.logging.Level; @Getter @@ -39,10 +36,15 @@ public final class SonarBukkit extends SonarBootstrap { public static SonarBukkit INSTANCE; public SonarBukkit(final @NotNull SonarBukkitPlugin plugin) { - super(plugin, plugin.getDataFolder(), new ActionBarVerbose(plugin.getServer())); + super(plugin, plugin.getDataFolder(), SonarPlatform.BUKKIT); INSTANCE = this; } + /** + * Wrapper for Bukkit audiences + */ + private final BukkitAudiences bukkitAudiences = BukkitAudiences.create(getPlugin()); + /** * Create a wrapper for the plugin logger, so we can use it outside * the velocity module. @@ -67,32 +69,18 @@ public void error(final String message, final Object... args) { } }; - /** - * Create a wrapper object for our server, so we can use it outside - * the velocity module. - *
- * We have to do this, so we can access all necessary API functions. - */ - public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUKKIT) { - - @Override - public Optional getOnlinePlayer(final String username) { - return getPlugin().getServer().getOnlinePlayers().stream() - .filter(player -> player.getName().equalsIgnoreCase(username)) - .findFirst() - .map(BukkitInvocationSource::new); - } - }; - @Override public void enable() { // Initialize bStats.org metrics - new Metrics(getPlugin(), getServer().getPlatform().getMetricsId()); + new Metrics(getPlugin(), getPlatform().getMetricsId()); // Register Sonar command Objects.requireNonNull(getPlugin().getCommand("sonar")).setExecutor(new BukkitSonarCommand()); + // Register audience register listener + getPlugin().getServer().getPluginManager().registerEvents(new AudienceListener(), getPlugin()); + // Register Fallback queue task getPlugin().getServer().getScheduler().runTaskTimerAsynchronously(getPlugin(), getFallback().getQueue()::poll, 10L, 10L); diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/audience/AudienceListener.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/audience/AudienceListener.java new file mode 100644 index 000000000..0c41ca75d --- /dev/null +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/audience/AudienceListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Sonar Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.jonesdev.sonar.bukkit.audience; + +import net.kyori.adventure.audience.Audience; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.NotNull; +import xyz.jonesdev.sonar.api.Sonar; +import xyz.jonesdev.sonar.bukkit.SonarBukkit; + +public final class AudienceListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void handle(final @NotNull PlayerJoinEvent event) { + final Audience audience = SonarBukkit.INSTANCE.getBukkitAudiences().player(event.getPlayer()); + Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void handle(final @NotNull PlayerQuitEvent event) { + Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName()); + } +} diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java index 32687422f..73c6faa90 100644 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java @@ -17,16 +17,14 @@ package xyz.jonesdev.sonar.bukkit.command; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.bukkit.SonarBukkit; public final class BukkitInvocationSource extends InvocationSource { - private static final BukkitAudiences AUDIENCES = BukkitAudiences.create(SonarBukkit.INSTANCE.getPlugin()); - public BukkitInvocationSource(final @NotNull CommandSender sender) { - super(sender.getName(), AUDIENCES.sender(sender)); + super(sender.getName(), SonarBukkit.INSTANCE.getBukkitAudiences().sender(sender), sender instanceof Player); } } diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java index 0993f1369..86c79d520 100644 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java @@ -18,27 +18,23 @@ package xyz.jonesdev.sonar.bukkit.command; import net.kyori.adventure.text.Component; -import org.bukkit.command.*; -import org.bukkit.entity.Player; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.Sonar; -import xyz.jonesdev.sonar.api.command.CommandInvocation; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; -import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument; import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static java.util.Collections.emptyList; public final class BukkitSonarCommand implements CommandExecutor, TabExecutor, SonarCommand { - { - cacheHelpMessage(); - } @Override public boolean onCommand(final CommandSender sender, @@ -48,7 +44,7 @@ public boolean onCommand(final CommandSender sender, // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new BukkitInvocationSource(sender); - if (!(sender instanceof ConsoleCommandSender)) { + if (invocationSource.isPlayer()) { // Check if the player actually has the permission to run the command if (!sender.hasPermission("sonar.command")) { invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); @@ -99,31 +95,6 @@ public boolean onCommand(final CommandSender sender, } } - subcommand.ifPresent(sub -> { - if (sub.getInfo().onlyPlayers() && !(sender instanceof Player)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly()); - return; - } - - if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly()); - return; - } - - final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args); - - // The subcommands has arguments which are not present in the executed command - if (sub.getInfo().arguments().length > 0 - && commandInvocation.getRawArguments().length <= 1) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage() - .replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")); - return; - } - - // Execute the sub command with the custom invocation properties - sub.execute(commandInvocation); - }); - if (!subcommand.isPresent()) { // Re-use the old, cached help message since we don't want to scan @@ -132,12 +103,14 @@ public boolean onCommand(final CommandSender sender, for (final Component component : CACHED_HELP_MESSAGE) { invocationSource.sendMessage(component); } + } else { + subcommand.get().invoke(invocationSource, args); } return false; } @Override - public List onTabComplete(final CommandSender sender, + public List onTabComplete(final @NotNull CommandSender sender, final Command command, final String commandAlias, final String @NotNull [] args) { @@ -145,33 +118,6 @@ public List onTabComplete(final CommandSender sender, if (!sender.hasPermission("sonar.command")) { return emptyList(); } - if (args.length <= 1) { - if (TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - TAB_SUGGESTIONS.add(subcommand.getInfo().name()); - - if (subcommand.getInfo().aliases().length > 0) { - TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases())); - } - } - } - return TAB_SUGGESTIONS; - } else if (args.length == 2) { - if (ARG_TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - final List parsedArguments = Arrays.stream(subcommand.getInfo().arguments()) - .map(Argument::value) - .collect(Collectors.toList()); - ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), parsedArguments); - for (final String alias : subcommand.getInfo().aliases()) { - ARG_TAB_SUGGESTIONS.put(alias, parsedArguments); - } - } - } - - final String subCommandName = args[0].toLowerCase(); - return ARG_TAB_SUGGESTIONS.getOrDefault(subCommandName, emptyList()); - } - return emptyList(); + return getCachedTabSuggestions(args); } } diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/verbose/ActionBarVerbose.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/verbose/ActionBarVerbose.java deleted file mode 100644 index 2d9bec6ac..000000000 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/verbose/ActionBarVerbose.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2023 Sonar Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package xyz.jonesdev.sonar.bukkit.verbose; - -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import xyz.jonesdev.sonar.api.verbose.Verbose; - -@RequiredArgsConstructor -public final class ActionBarVerbose extends Verbose { - private final Server server; - - @Override - public void broadcast(final Component component) { - synchronized (subscribers) { - for (final String subscriber : subscribers) { - final Player player = server.getPlayer(subscriber); - if (player != null) { - // TODO: use an adventure audience - } - } - } - } -} diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java index 10b0eb0a7..9d7138f73 100644 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/SonarBungee.java @@ -18,22 +18,19 @@ package xyz.jonesdev.sonar.bungee; import lombok.Getter; +import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import org.bstats.bungeecord.Metrics; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.SonarPlatform; -import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter; import xyz.jonesdev.sonar.api.logger.LoggerWrapper; -import xyz.jonesdev.sonar.api.server.ServerWrapper; -import xyz.jonesdev.sonar.bungee.command.BungeeInvocationSource; +import xyz.jonesdev.sonar.bungee.audience.AudienceListener; import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand; import xyz.jonesdev.sonar.bungee.fallback.FallbackListener; import xyz.jonesdev.sonar.bungee.fallback.injection.BaseInjectionHelper; import xyz.jonesdev.sonar.bungee.fallback.injection.ChildChannelInitializer; -import xyz.jonesdev.sonar.bungee.verbose.VerboseWrapper; import xyz.jonesdev.sonar.common.boot.SonarBootstrap; -import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -42,10 +39,15 @@ public final class SonarBungee extends SonarBootstrap { public static SonarBungee INSTANCE; public SonarBungee(final @NotNull SonarBungeePlugin plugin) { - super(plugin, plugin.getDataFolder(), new VerboseWrapper(plugin.getServer())); + super(plugin, plugin.getDataFolder(), SonarPlatform.BUNGEE); INSTANCE = this; } + /** + * Wrapper for BungeeCord audiences + */ + private final BungeeAudiences bungeeAudiences = BungeeAudiences.create(getPlugin()); + /** * Create a wrapper for the plugin logger, so we can use it outside * the velocity module. @@ -70,28 +72,11 @@ public void error(final String message, final Object... args) { } }; - /** - * Create a wrapper object for our server, so we can use it outside - * the velocity module. - *
- * We have to do this, so we can access all necessary API functions. - */ - public final ServerWrapper server = new ServerWrapper(SonarPlatform.BUNGEE) { - - @Override - public Optional getOnlinePlayer(final String username) { - return getPlugin().getServer().getPlayers().stream() - .filter(player -> player.getName().equalsIgnoreCase(username)) - .findFirst() - .map(BungeeInvocationSource::new); - } - }; - @Override public void enable() { // Initialize bStats.org metrics - new Metrics(getPlugin(), getServer().getPlatform().getMetricsId()); + new Metrics(getPlugin(), getPlatform().getMetricsId()); // Register Sonar command getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand()); @@ -99,6 +84,9 @@ public void enable() { // Register Fallback listener getPlugin().getServer().getPluginManager().registerListener(getPlugin(), new FallbackListener(getFallback())); + // Register audience register listener + getPlugin().getServer().getPluginManager().registerListener(getPlugin(), new AudienceListener()); + // Register Fallback queue task getPlugin().getServer().getScheduler().schedule(getPlugin(), getFallback().getQueue()::poll, 500L, 500L, TimeUnit.MILLISECONDS); diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/audience/AudienceListener.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/audience/AudienceListener.java new file mode 100644 index 000000000..64038d757 --- /dev/null +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/audience/AudienceListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Sonar Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.jonesdev.sonar.bungee.audience; + +import net.kyori.adventure.audience.Audience; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.event.EventPriority; +import org.jetbrains.annotations.NotNull; +import xyz.jonesdev.sonar.api.Sonar; +import xyz.jonesdev.sonar.bungee.SonarBungee; + +public final class AudienceListener implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void handle(final @NotNull PostLoginEvent event) { + final Audience audience = SonarBungee.INSTANCE.getBungeeAudiences().player(event.getPlayer()); + Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void handle(final @NotNull PlayerDisconnectEvent event) { + Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName()); + } +} diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java index 492a7b14a..e40835c12 100644 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java @@ -17,16 +17,14 @@ package xyz.jonesdev.sonar.bungee.command; -import net.kyori.adventure.platform.bungeecord.BungeeAudiences; import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.connection.ProxiedPlayer; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.bungee.SonarBungee; public final class BungeeInvocationSource extends InvocationSource { - private static final BungeeAudiences AUDIENCES = BungeeAudiences.create(SonarBungee.INSTANCE.getPlugin()); - public BungeeInvocationSource(final @NotNull CommandSender sender) { - super(sender.getName(), AUDIENCES.sender(sender)); + super(sender.getName(), SonarBungee.INSTANCE.getBungeeAudiences().sender(sender), sender instanceof ProxiedPlayer); } } diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java index efc083534..f49b9b31c 100644 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java @@ -19,29 +19,22 @@ import net.kyori.adventure.text.Component; import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; -import net.md_5.bungee.command.ConsoleCommandSender; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.Sonar; -import xyz.jonesdev.sonar.api.command.CommandInvocation; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; -import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument; import java.util.Arrays; -import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static java.util.Collections.emptyList; public final class BungeeSonarCommand extends Command implements TabExecutor, SonarCommand { public BungeeSonarCommand() { super("sonar"); - cacheHelpMessage(); } @Override @@ -50,7 +43,7 @@ public void execute(final @NotNull CommandSender sender, final String[] args) { // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new BungeeInvocationSource(sender); - if (!(sender instanceof ConsoleCommandSender)) { + if (invocationSource.isPlayer()) { // Check if the player actually has the permission to run the command if (!sender.hasPermission("sonar.command")) { invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); @@ -101,31 +94,6 @@ public void execute(final @NotNull CommandSender sender, final String[] args) { } } - subcommand.ifPresent(sub -> { - if (sub.getInfo().onlyPlayers() && !(sender instanceof ProxiedPlayer)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly()); - return; - } - - if (sub.getInfo().onlyConsole() && !(sender instanceof ConsoleCommandSender)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly()); - return; - } - - final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, args); - - // The subcommands has arguments which are not present in the executed command - if (sub.getInfo().arguments().length > 0 - && commandInvocation.getRawArguments().length <= 1) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage() - .replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")); - return; - } - - // Execute the sub command with the custom invocation properties - sub.execute(commandInvocation); - }); - if (!subcommand.isPresent()) { // Re-use the old, cached help message since we don't want to scan @@ -134,42 +102,17 @@ public void execute(final @NotNull CommandSender sender, final String[] args) { for (final Component component : CACHED_HELP_MESSAGE) { invocationSource.sendMessage(component); } + } else { + subcommand.get().invoke(invocationSource, args); } } @Override - public Iterable onTabComplete(final CommandSender sender, final String @NotNull [] args) { + public Iterable onTabComplete(final @NotNull CommandSender sender, final String @NotNull [] args) { // Do not allow tab completion if the player does not have the required permission if (!sender.hasPermission("sonar.command")) { return emptyList(); } - if (args.length <= 1) { - if (TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - TAB_SUGGESTIONS.add(subcommand.getInfo().name()); - - if (subcommand.getInfo().aliases().length > 0) { - TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases())); - } - } - } - return TAB_SUGGESTIONS; - } else if (args.length == 2) { - if (ARG_TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - final List parsedArguments = Arrays.stream(subcommand.getInfo().arguments()) - .map(Argument::value) - .collect(Collectors.toList()); - ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), parsedArguments); - for (final String alias : subcommand.getInfo().aliases()) { - ARG_TAB_SUGGESTIONS.put(alias, parsedArguments); - } - } - } - - final String subCommandName = args[0].toLowerCase(); - return ARG_TAB_SUGGESTIONS.getOrDefault(subCommandName, emptyList()); - } - return emptyList(); + return getCachedTabSuggestions(args); } } diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/verbose/VerboseWrapper.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/verbose/VerboseWrapper.java deleted file mode 100644 index b57f01774..000000000 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/verbose/VerboseWrapper.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2023 Sonar Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package xyz.jonesdev.sonar.bungee.verbose; - -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import xyz.jonesdev.sonar.api.verbose.Verbose; - -@RequiredArgsConstructor -public final class VerboseWrapper extends Verbose { - private final ProxyServer server; - - @Override - public void broadcast(final Component component) { - synchronized (subscribers) { - for (final String subscriber : subscribers) { - final ProxiedPlayer player = server.getPlayer(subscriber); - if (player != null) { - // TODO: use an adventure audience - } - } - } - } -} diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java index c18dbe1f6..fdf2ff623 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/boot/SonarBootstrap.java @@ -24,6 +24,7 @@ import xyz.jonesdev.cappuccino.Cappuccino; import xyz.jonesdev.cappuccino.ExpiringCache; import xyz.jonesdev.sonar.api.Sonar; +import xyz.jonesdev.sonar.api.SonarPlatform; import xyz.jonesdev.sonar.api.SonarSupplier; import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry; import xyz.jonesdev.sonar.api.config.SonarConfiguration; @@ -48,19 +49,21 @@ public abstract class SonarBootstrap implements Sonar { private SonarConfiguration config; private VerifiedPlayerController verifiedPlayerController; private File dataDirectory; + private final SonarPlatform platform; private final SubcommandRegistry subcommandRegistry; private final SystemTimer launchTimer = new SystemTimer(); public SonarBootstrap(final @NotNull T plugin, final File dataDirectory, - final Verbose verboseHandler) { - // Set the API to this instance so the config doesn't have issues + final SonarPlatform platform) { + // Set the Sonar API SonarSupplier.set(this); // Set the plugin instance before anything else this.plugin = plugin; this.dataDirectory = dataDirectory; - this.verboseHandler = verboseHandler; + this.platform = platform; + this.verboseHandler = new Verbose(); this.config = new SonarConfiguration(dataDirectory); this.subcommandRegistry = new SubcommandRegistryHolder(); } diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/BlacklistCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/BlacklistCommand.java index 12de7a872..74057daac 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/BlacklistCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/BlacklistCommand.java @@ -39,7 +39,7 @@ public final class BlacklistCommand extends Subcommand { @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { switch (invocation.getRawArguments()[1].toLowerCase()) { case "add": { if (invocation.getRawArguments().length <= 2) { diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/DumpCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/DumpCommand.java index c9bb6e1bc..ea89e71c0 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/DumpCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/DumpCommand.java @@ -46,7 +46,7 @@ public final class DumpCommand extends Subcommand implements JVMProfiler { .create(); @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { final String json = GSON.toJson(collectMappedInformation()); SONAR.getLogger().info("Generated dump: {}", json); } @@ -56,7 +56,7 @@ public void execute(final @NotNull CommandInvocation invocation) { val mappings = new WeakHashMap(); mappings.put("sonar", new Dump.Sonar( SONAR.getVersion().getFull(), - SONAR.getServer().getPlatform(), + SONAR.getPlatform(), SONAR.getVersion().isOnMainBranch() )); mappings.put("runtime", new Dump.Runtime( diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/LockdownCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/LockdownCommand.java index 970a8fceb..39884a91c 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/LockdownCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/LockdownCommand.java @@ -29,7 +29,7 @@ public final class LockdownCommand extends Subcommand { @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { final boolean newState = !SONAR.getConfig().getLockdown().isEnabled(); SONAR.getConfig().getLockdown().setEnabled(newState); SONAR.getConfig().getGeneralConfig().set("general.lockdown.enabled", newState); diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/ReloadCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/ReloadCommand.java index 427d122ca..a4d84724f 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/ReloadCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/ReloadCommand.java @@ -30,7 +30,7 @@ public final class ReloadCommand extends Subcommand { @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { final SystemTimer timer = new SystemTimer(); invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getReloading()); diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/StatisticsCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/StatisticsCommand.java index 242f2e2e4..577fe2c93 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/StatisticsCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/StatisticsCommand.java @@ -48,7 +48,7 @@ private enum StatisticType { } @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { StatisticType type = StatisticType.GENERAL; if (invocation.getRawArguments().length >= 2) { try { diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerboseCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerboseCommand.java index 0ad3ee919..a5962772f 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerboseCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerboseCommand.java @@ -17,46 +17,30 @@ package xyz.jonesdev.sonar.common.subcommand.impl; +import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.command.CommandInvocation; -import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo; -import java.util.Optional; - @SubcommandInfo( name = "verbose", - description = "Enable and disable Sonar verbose", + description = "Enable or disable Sonar verbose", onlyPlayers = true ) public final class VerboseCommand extends Subcommand { @Override - public void execute(final @NotNull CommandInvocation invocation) { - InvocationSource verboseSubscriber = invocation.getSender(); - - // Support for '/sonar verbose [username]' - if (invocation.getRawArguments().length >= 2) { - final Optional optional = SONAR.getServer().getOnlinePlayer(invocation.getRawArguments()[1]); - if (optional.isPresent()) verboseSubscriber = optional.get(); - } - - if (SONAR.getVerboseHandler().isSubscribed(verboseSubscriber.getName())) { - SONAR.getVerboseHandler().unsubscribe(verboseSubscriber.getName()); - if (verboseSubscriber != invocation.getSender()) { - verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribedOther() - .replace("%player%", verboseSubscriber.getName())); - } - verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribed()); + protected void execute(final @NotNull CommandInvocation invocation) { + if (SONAR.getVerboseHandler().isSubscribed(invocation.getSender().getName())) { + SONAR.getVerboseHandler().unsubscribe(invocation.getSender().getName()); + invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribed()); + // Reset ActionBar component when unsubscribing + invocation.getSender().getAudience().sendActionBar(Component.empty()); return; } - if (verboseSubscriber != invocation.getSender()) { - verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribedOther() - .replace("%player%", verboseSubscriber.getName())); - } - verboseSubscriber.sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribed()); - SONAR.getVerboseHandler().subscribe(verboseSubscriber.getName()); + invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribed()); + SONAR.getVerboseHandler().subscribe(invocation.getSender().getName()); } } diff --git a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerifiedCommand.java b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerifiedCommand.java index 626130cea..49846b350 100644 --- a/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerifiedCommand.java +++ b/sonar-common/src/main/java/xyz/jonesdev/sonar/common/subcommand/impl/VerifiedCommand.java @@ -43,7 +43,7 @@ public final class VerifiedCommand extends Subcommand { private static final Queue LOCK = new LinkedBlockingQueue<>(1); @Override - public void execute(final @NotNull CommandInvocation invocation) { + protected void execute(final @NotNull CommandInvocation invocation) { switch (invocation.getRawArguments()[1].toLowerCase()) { case "history": { if (invocation.getRawArguments().length <= 2) { diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/SonarVelocity.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/SonarVelocity.java index 0e98636ff..50d97bfef 100644 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/SonarVelocity.java +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/SonarVelocity.java @@ -20,17 +20,13 @@ import lombok.Getter; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.SonarPlatform; -import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.fallback.traffic.TrafficCounter; import xyz.jonesdev.sonar.api.logger.LoggerWrapper; -import xyz.jonesdev.sonar.api.server.ServerWrapper; import xyz.jonesdev.sonar.common.boot.SonarBootstrap; -import xyz.jonesdev.sonar.velocity.command.VelocityInvocationSource; +import xyz.jonesdev.sonar.velocity.audience.AudienceListener; import xyz.jonesdev.sonar.velocity.command.VelocitySonarCommand; import xyz.jonesdev.sonar.velocity.fallback.FallbackListener; -import xyz.jonesdev.sonar.velocity.verbose.VerboseWrapper; -import java.util.Optional; import java.util.concurrent.TimeUnit; @Getter @@ -38,7 +34,7 @@ public final class SonarVelocity extends SonarBootstrap { public static SonarVelocity INSTANCE; public SonarVelocity(final @NotNull SonarVelocityPlugin plugin) { - super(plugin, plugin.getDataDirectory().toFile(), new VerboseWrapper(plugin.getServer())); + super(plugin, plugin.getDataDirectory().toFile(), SonarPlatform.VELOCITY); INSTANCE = this; } @@ -66,28 +62,11 @@ public void error(final String message, final Object... args) { } }; - /** - * Create a wrapper object for our server, so we can use it outside - * the velocity module. - *
- * We have to do this, so we can access all necessary API functions. - */ - public final ServerWrapper server = new ServerWrapper(SonarPlatform.VELOCITY) { - - @Override - public Optional getOnlinePlayer(final String username) { - return getPlugin().getServer().getAllPlayers().stream() - .filter(player -> player.getUsername().equalsIgnoreCase(username)) - .findFirst() - .map(VelocityInvocationSource::new); - } - }; - @Override public void enable() { // Initialize bStats.org metrics - getPlugin().getMetricsFactory().make(getPlugin(), getServer().getPlatform().getMetricsId()); + getPlugin().getMetricsFactory().make(getPlugin(), getPlatform().getMetricsId()); // Register Sonar command getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand()); @@ -95,6 +74,9 @@ public void enable() { // Register Fallback listener getPlugin().getServer().getEventManager().register(getPlugin(), new FallbackListener(getFallback())); + // Register audience register listener + getPlugin().getServer().getEventManager().register(getPlugin(), new AudienceListener()); + // Register Fallback queue task getPlugin().getServer().getScheduler().buildTask(getPlugin(), getFallback().getQueue()::poll) .repeat(500L, TimeUnit.MILLISECONDS) diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/audience/AudienceListener.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/audience/AudienceListener.java new file mode 100644 index 000000000..f489a4753 --- /dev/null +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/audience/AudienceListener.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Sonar Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.jonesdev.sonar.velocity.audience; + +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import org.jetbrains.annotations.NotNull; +import xyz.jonesdev.sonar.api.Sonar; + +public final class AudienceListener { + + @Subscribe(order = PostOrder.LAST) + public void handle(final @NotNull PostLoginEvent event) { + Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getUsername(), event.getPlayer()); + } + + @Subscribe(order = PostOrder.LAST) + public void handle(final @NotNull DisconnectEvent event) { + Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getUsername()); + } +} diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java index a299ff3fe..2779db459 100644 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java @@ -24,8 +24,6 @@ public final class VelocityInvocationSource extends InvocationSource { public VelocityInvocationSource(final @NotNull CommandSource sender) { - super(sender instanceof Player - ? ((Player) sender).getUsername() - : "Console", sender); + super(sender instanceof Player ? ((Player) sender).getUsername() : "Console", sender, sender instanceof Player); } } diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java index 87d11cfe9..554cdf362 100644 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java @@ -18,35 +18,27 @@ package xyz.jonesdev.sonar.velocity.command; import com.velocitypowered.api.command.SimpleCommand; -import com.velocitypowered.api.proxy.ConsoleCommandSource; -import com.velocitypowered.api.proxy.Player; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; import xyz.jonesdev.sonar.api.Sonar; -import xyz.jonesdev.sonar.api.command.CommandInvocation; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; -import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument; import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static java.util.Collections.emptyList; public final class VelocitySonarCommand implements SimpleCommand, SonarCommand { - { - cacheHelpMessage(); - } @Override public void execute(final @NotNull Invocation invocation) { // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new VelocityInvocationSource(invocation.source()); - if (!(invocation.source() instanceof ConsoleCommandSource)) { + if (invocationSource.isPlayer()) { // Check if the player actually has the permission to run the command if (!invocation.source().hasPermission("sonar.command")) { invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); @@ -97,32 +89,6 @@ public void execute(final @NotNull Invocation invocation) { } } - subcommand.ifPresent(sub -> { - if (sub.getInfo().onlyPlayers() && !(invocation.source() instanceof Player)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly()); - return; - } - - if (sub.getInfo().onlyConsole() && !(invocation.source() instanceof ConsoleCommandSource)) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly()); - return; - } - - final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, sub, invocation.arguments()); - - // The subcommands has arguments which are not present in the executed command - if (sub.getInfo().arguments().length > 0 - && commandInvocation.getRawArguments().length <= 1 - && sub.getInfo().argumentsRequired()) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage() - .replace("%usage%", sub.getInfo().name() + " (" + sub.getArguments() + ")")); - return; - } - - // Execute the sub command with the custom invocation properties - sub.execute(commandInvocation); - }); - if (subcommand.isEmpty()) { // Re-use the old, cached help message since we don't want to scan @@ -131,6 +97,8 @@ public void execute(final @NotNull Invocation invocation) { for (final Component component : CACHED_HELP_MESSAGE) { invocationSource.sendMessage(component); } + } else { + subcommand.get().invoke(invocationSource, invocation.arguments()); } } @@ -140,33 +108,6 @@ public List suggest(final @NotNull Invocation invocation) { if (!invocation.source().hasPermission("sonar.command")) { return emptyList(); } - if (invocation.arguments().length <= 1) { - if (TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - TAB_SUGGESTIONS.add(subcommand.getInfo().name()); - - if (subcommand.getInfo().aliases().length > 0) { - TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases())); - } - } - } - return TAB_SUGGESTIONS; - } else if (invocation.arguments().length == 2) { - if (ARG_TAB_SUGGESTIONS.isEmpty()) { - for (final Subcommand subcommand : Sonar.get().getSubcommandRegistry().getSubcommands()) { - final List parsedArguments = Arrays.stream(subcommand.getInfo().arguments()) - .map(Argument::value) - .collect(Collectors.toUnmodifiableList()); - ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), parsedArguments); - for (final String alias : subcommand.getInfo().aliases()) { - ARG_TAB_SUGGESTIONS.put(alias, parsedArguments); - } - } - } - - final String subCommandName = invocation.arguments()[0].toLowerCase(); - return ARG_TAB_SUGGESTIONS.getOrDefault(subCommandName, emptyList()); - } - return emptyList(); + return getCachedTabSuggestions(invocation.arguments()); } } diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/verbose/VerboseWrapper.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/verbose/VerboseWrapper.java deleted file mode 100644 index 07dca3f35..000000000 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/verbose/VerboseWrapper.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 Sonar Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package xyz.jonesdev.sonar.velocity.verbose; - -import com.velocitypowered.api.proxy.ProxyServer; -import lombok.RequiredArgsConstructor; -import net.kyori.adventure.text.Component; -import xyz.jonesdev.sonar.api.verbose.Verbose; - -@RequiredArgsConstructor -public final class VerboseWrapper extends Verbose { - private final ProxyServer server; - - @Override - public void broadcast(final Component component) { - synchronized (subscribers) { - for (final String subscriber : subscribers) { - server.getPlayer(subscriber).ifPresent(player -> { - player.sendActionBar(component); - }); - } - } - } -}