diff --git a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java index 044d3e692..8ab7f5414 100644 --- a/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java +++ b/src/main/java/org/mvplugins/multiverse/core/command/MVCommandCompletions.java @@ -100,7 +100,7 @@ public class MVCommandCompletions extends PaperCommandCompletions { registerAsyncCompletion("mvworlds", this::suggestMVWorlds); registerAsyncCompletion("mvworldpropsname", this::suggestMVWorldPropsName); registerAsyncCompletion("mvworldpropsvalue", this::suggestMVWorldPropsValue); - registerAsyncCompletion("playersarray", this::suggestPlayersArray); + registerCompletion("playersarray", this::suggestPlayersArray); // getting online players cannot be async registerStaticCompletion("propsmodifyaction", suggestEnums(PropertyModifyAction.class)); registerStaticCompletion("spawncategories", suggestEnums(SpawnCategory.class)); registerAsyncCompletion("spawncategorypropsname", this::suggestSpawnCategoryPropsName); diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java index 7527152cc..06683edc1 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/BedDestination.java @@ -6,7 +6,7 @@ import co.aikar.locales.MessageKey; import co.aikar.locales.MessageKeyProvider; -import org.bukkit.Bukkit; +import jakarta.inject.Inject; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -20,8 +20,7 @@ import org.mvplugins.multiverse.core.utils.PlayerFinder; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.utils.result.FailureReason; - -import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; +import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker; /** * {@link Destination} implementation for beds. @@ -30,7 +29,11 @@ public final class BedDestination implements Destination { static final String OWN_BED_STRING = "playerbed"; - BedDestination() { + private final ConcurrentPlayerWorldTracker worldTracker; + + @Inject + BedDestination(@NotNull ConcurrentPlayerWorldTracker worldTracker) { + this.worldTracker = worldTracker; } /** @@ -62,8 +65,8 @@ public final class BedDestination implements Destination suggestDestinations( @NotNull CommandSender sender, @Nullable String destinationParams) { - List collect = Bukkit.getOnlinePlayers().stream() - .map(player -> new DestinationSuggestionPacket(this, player.getName(), player.getName())) + List collect = worldTracker.getOnlinePlayers().stream() + .map(player -> new DestinationSuggestionPacket(this, player, player)) .collect(Collectors.toList()); if (sender instanceof Player) { collect.add(new DestinationSuggestionPacket(this, OWN_BED_STRING, OWN_BED_STRING)); diff --git a/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java b/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java index b09d3c151..36851322c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java +++ b/src/main/java/org/mvplugins/multiverse/core/destination/core/PlayerDestination.java @@ -4,7 +4,7 @@ import co.aikar.locales.MessageKey; import co.aikar.locales.MessageKeyProvider; -import org.bukkit.Bukkit; +import jakarta.inject.Inject; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -14,21 +14,26 @@ import org.mvplugins.multiverse.core.destination.Destination; import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket; import org.mvplugins.multiverse.core.locale.MVCorei18n; -import org.mvplugins.multiverse.core.locale.message.MessageReplacement; import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace; import org.mvplugins.multiverse.core.utils.PlayerFinder; import org.mvplugins.multiverse.core.utils.result.Attempt; import org.mvplugins.multiverse.core.utils.result.FailureReason; +import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker; /** * {@link Destination} implementation for players.s */ @Service public final class PlayerDestination implements Destination { + + private final ConcurrentPlayerWorldTracker playerWorldTracker; + /** * Creates a new instance of the PlayerDestination. */ - PlayerDestination() { + @Inject + PlayerDestination(@NotNull ConcurrentPlayerWorldTracker playerWorldTracker) { + this.playerWorldTracker = playerWorldTracker; } /** @@ -60,8 +65,8 @@ public final class PlayerDestination implements Destination suggestDestinations( @NotNull CommandSender sender, @Nullable String destinationParams) { - return Bukkit.getOnlinePlayers().stream() - .map(p -> new DestinationSuggestionPacket(this, p.getName(), p.getName())) + return playerWorldTracker.getOnlinePlayers().stream() + .map(player -> new DestinationSuggestionPacket(this, player, player)) .toList(); } diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java index 948c990af..2c3c5ad0c 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVChatListener.java @@ -16,6 +16,7 @@ import org.mvplugins.multiverse.core.dynamiclistener.annotations.SkipIfEventExist; import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter; import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.core.world.helpers.ConcurrentPlayerWorldTracker; /** * Multiverse's Listener for players. @@ -24,16 +25,17 @@ final class MVChatListener implements CoreListener { private final CoreConfig config; private final WorldManager worldManager; - private final MVPlayerListener playerListener; + private final ConcurrentPlayerWorldTracker playerWorldTracker; @Inject MVChatListener( CoreConfig config, WorldManager worldManager, - MVPlayerListener playerListener) { + ConcurrentPlayerWorldTracker playerWorldTracker + ) { this.config = config; this.worldManager = worldManager; - this.playerListener = playerListener; + this.playerWorldTracker = playerWorldTracker; } @EventClass("io.papermc.paper.event.player.AsyncChatEvent") @@ -94,12 +96,9 @@ void asyncPlayerChat(AsyncPlayerChatEvent event) { } private String getWorldName(Player player) { - String world = playerListener.getPlayerWorld().get(player.getName()); - if (world == null) { - world = player.getWorld().getName(); - playerListener.getPlayerWorld().put(player.getName(), world); - } - return this.worldManager.getLoadedWorld(world) + String worldName = playerWorldTracker.getPlayerWorld(player.getName()) + .getOrElse(() -> player.getWorld().getName()); + return this.worldManager.getLoadedWorld(worldName) .map(mvworld -> mvworld.isHidden() ? "" : mvworld.getAliasOrName()) .getOrElse(""); } diff --git a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java index 8dac371f0..046c4ec21 100644 --- a/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java +++ b/src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java @@ -73,8 +73,6 @@ final class MVPlayerListener implements CoreListener { private final DimensionFinder dimensionFinder; private final CorePermissionsChecker corePermissionsChecker; - private final Map playerWorld = new ConcurrentHashMap<>(); - @Inject MVPlayerListener( MultiverseCore plugin, @@ -117,15 +115,6 @@ private PluginLocales getLocales() { return getCommandManager().getLocales(); } - /** - * Gets the map of player and the world name they are in. - * - * @return the playerWorld-map - */ - Map getPlayerWorld() { - return playerWorld; - } - /** * This method is called when a player respawns. * @@ -274,7 +263,6 @@ private void handleJoinLocation(PlayerSpawnLocationEvent event) { void playerChangedWorld(PlayerChangedWorldEvent event) { // Permissions now determine whether or not to handle a gamemode. this.handleGameModeAndFlight(event.getPlayer(), event.getPlayer().getWorld()); - playerWorld.put(event.getPlayer().getName(), event.getPlayer().getWorld().getName()); } /** diff --git a/src/main/java/org/mvplugins/multiverse/core/world/helpers/ConcurrentPlayerWorldTracker.java b/src/main/java/org/mvplugins/multiverse/core/world/helpers/ConcurrentPlayerWorldTracker.java new file mode 100644 index 000000000..8c9c66300 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/core/world/helpers/ConcurrentPlayerWorldTracker.java @@ -0,0 +1,90 @@ +package org.mvplugins.multiverse.core.world.helpers; + +import io.vavr.control.Option; +import jakarta.inject.Inject; +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.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.MultiverseCore; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Tracks which players are in which worlds, using a thread-safe map. + * This allows async access to online players list and the world they are in. + * + * @since 5.4 + */ +@ApiStatus.AvailableSince("5.4") +@Service +public final class ConcurrentPlayerWorldTracker implements Listener { + + private final Map playerWorldMap; + + @Inject + ConcurrentPlayerWorldTracker(@NotNull MultiverseCore plugin) { + this.playerWorldMap = new ConcurrentHashMap<>(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * Get an unmodifiable collection of all online player names on the server. + * + * @return Unmodifiable collection of online player names. + * + * @since 5.4 + */ + @ApiStatus.AvailableSince("5.4") + @NotNull + @UnmodifiableView + public Collection getOnlinePlayers() { + return Collections.unmodifiableCollection(playerWorldMap.keySet()); + } + + /** + * Get the world name a player is currently in. + * + * @param playerName Name of the player. + * @return World name the player is in, or null if the player is not online. + * + * @since 5.4 + */ + @ApiStatus.AvailableSince("5.4") + @NotNull + public Option getPlayerWorld(String playerName) { + return Option.of(playerWorldMap.get(playerName)); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onPlayerJoin(PlayerJoinEvent event) { + setPlayerWorld(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.LOWEST) + private void onPlayerChangedWorld(@NotNull PlayerChangedWorldEvent event) { + setPlayerWorld(event.getPlayer()); + } + + private void setPlayerWorld(Player player) { + String playerName = player.getName(); + String worldName = player.getWorld().getName(); + playerWorldMap.put(playerName, worldName); + } + + @EventHandler + private void onPlayerQuit(@NotNull PlayerQuitEvent event) { + String playerName = event.getPlayer().getName(); + playerWorldMap.remove(playerName); + } +}