diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java index 5899dafa3..0aab7bd5f 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java @@ -18,6 +18,7 @@ import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; import vg.civcraft.mc.civmodcore.inventory.items.MoreTags; import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; import vg.civcraft.mc.civmodcore.inventory.items.TreeTypeUtils; +import vg.civcraft.mc.civmodcore.players.PlayerNames; import vg.civcraft.mc.civmodcore.players.scoreboard.bottom.BottomLineAPI; import vg.civcraft.mc.civmodcore.players.scoreboard.side.ScoreBoardAPI; import vg.civcraft.mc.civmodcore.players.scoreboard.side.ScoreBoardListener; @@ -88,6 +89,7 @@ public class CivModCorePlugin extends ACivMod { registerListener(DialogManager.INSTANCE); registerListener(new ScoreBoardListener()); registerListener(new WorldTracker()); + registerListener(new PlayerNames()); // Register commands this.commands = new CommandManager(this); this.commands.init(); diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java index 85c8fb0cb..70a74b046 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java @@ -6,9 +6,11 @@ import co.aikar.commands.InvalidCommandArgument; import java.util.Arrays; import java.util.List; import org.bukkit.Material; +import org.bukkit.OfflinePlayer; import org.bukkit.command.ConsoleCommandSender; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.players.PlayerNames; /** * This is a separate helper class that allows plugins to use ACF without needing to commit to {@link CommandManager} @@ -86,6 +88,22 @@ public final class CommandHelpers { ); } + /** + * Completion for all known player names. + */ + public static void registerKnownPlayersCompletion( + final @NotNull CommandCompletions completions + ) { + completions.registerCompletion( + "allplayers", + (context) -> PlayerNames.getPlayerNames() + ); + completions.setDefaultCompletion( + "allplayers", + OfflinePlayer.class + ); + } + // ============================================================ // Contexts // ============================================================ diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java index f60edf7ad..f28fb90fa 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java @@ -10,25 +10,15 @@ import co.aikar.commands.CommandContexts; import com.google.common.base.Strings; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; import java.util.logging.Level; import javax.annotation.Nonnull; import org.apache.commons.lang3.reflect.FieldUtils; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.plugin.Plugin; import vg.civcraft.mc.civmodcore.utilities.CivLogger; @@ -36,9 +26,6 @@ import vg.civcraft.mc.civmodcore.utilities.CivLogger; * Command registration class wrapper around {@link BukkitCommandManager}. */ public class CommandManager extends BukkitCommandManager { - // Track players to offer quick completion where necessary. - private final Set autocompletePlayerNames = new ConcurrentSkipListSet<>(); - private final CivLogger logger; /** @@ -57,25 +44,6 @@ public class CommandManager extends BukkitCommandManager { * {@link #unregisterCompletions()}, otherwise there may be issues. */ public final void init() { - // Prepare our list with player names on init. - // Load all known players once on initialization, then use a loginlistener to update the existing name set. - Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName) - .filter(Objects::nonNull) - .forEach(autocompletePlayerNames::add); - /*TODO - this may be better solved with a single global listener, but the implications would've needed some checks. - This is pretty cheap and works fast. - */ - - Bukkit.getPluginManager().registerEvents(new Listener() { - // Players joining should be added to our list, just in case they are new. - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onLogin(PlayerLoginEvent ev) { - // For autocomplete we wil update the listeners. - autocompletePlayerNames.add(ev.getPlayer().getName()); - } - }, plugin); - registerContexts(getCommandContexts()); registerCompletions(getCommandCompletions()); registerCommands(); @@ -100,11 +68,7 @@ public class CommandManager extends BukkitCommandManager { CommandHelpers.registerNoneCompletion(completions); CommandHelpers.registerMaterialsCompletion(completions); CommandHelpers.registerItemMaterialsCompletion(completions); - - // Completion lists are copied so outer code can modify the lists without breaking our inner contracts, - // namely that all players should be searchable by completion. - // Using Collections.immutableList is an alternative, but both variants aren't expensive. - completions.registerAsyncCompletion("allplayers", (context) -> new ArrayList<>(autocompletePlayerNames)); + CommandHelpers.registerKnownPlayersCompletion(completions); } /** diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java new file mode 100644 index 000000000..99dc6af16 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java @@ -0,0 +1,45 @@ +package vg.civcraft.mc.civmodcore.players; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerLoginEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +public final class PlayerNames implements Listener { + + private static final Set names = new HashSet<>(); + + public PlayerNames() { + names.clear(); + names.addAll( + Stream.of(Bukkit.getOfflinePlayers()) + .map(OfflinePlayer::getName) + .filter(StringUtils::isNotBlank) + .toList() + ); + } + + @EventHandler( + priority = EventPriority.MONITOR, // Make sure it happens after NameLayer's AssociationListener + ignoreCancelled = true + ) + private void onLogin( + final @NotNull PlayerLoginEvent event + ) { + names.add(event.getPlayer().getName()); + } + + public static @NotNull Collection getPlayerNames() { + return Collections.unmodifiableSet(names); + } +}