diff --git a/bukkit/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java b/bukkit/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java index 4d64117..8a2d935 100644 --- a/bukkit/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java +++ b/bukkit/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java @@ -19,10 +19,11 @@ public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); Object playerId = platform.getPlayerId(player.getName(), player.getUniqueId()); - if(! platform.getQueuedPlayers().containsKey(playerId)) { + Integer queuedId = platform.getQueuedPlayers().get(playerId); + if (queuedId == null) { return; } - platform.handleOnlineCommands(new QueuedPlayer(platform.getQueuedPlayers().get(playerId), player.getName(), player.getUniqueId().toString())); + platform.handleOnlineCommands(new QueuedPlayer(queuedId, player.getName(), player.getUniqueId().toString())); } } diff --git a/folia/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java b/folia/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java index c214a39..e4706d6 100644 --- a/folia/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java +++ b/folia/src/main/java/io/tebex/plugin/event/PlayerJoinListener.java @@ -22,10 +22,11 @@ public void onPlayerJoin(PlayerJoinEvent event) { Object playerId = platform.getPlayerId(player.getName(), player.getUniqueId()); platform.getJoinEvents().add(new ServerEvent(player.getUniqueId().toString(), player.getName(), player.getAddress().getAddress().getHostAddress(), EnumServerEventType.JOIN)); - if(! platform.getQueuedPlayers().containsKey(playerId)) { + Integer queuedId = platform.getQueuedPlayers().get(playerId); + if (queuedId == null) { return; } - platform.handleOnlineCommands(new QueuedPlayer(platform.getQueuedPlayers().get(playerId), player.getName(), player.getUniqueId().toString())); + platform.handleOnlineCommands(new QueuedPlayer(queuedId, player.getName(), player.getUniqueId().toString())); } } diff --git a/sdk/src/main/java/io/tebex/sdk/platform/BasePluginPlatform.java b/sdk/src/main/java/io/tebex/sdk/platform/BasePluginPlatform.java index e7b685c..cda1cfe 100644 --- a/sdk/src/main/java/io/tebex/sdk/platform/BasePluginPlatform.java +++ b/sdk/src/main/java/io/tebex/sdk/platform/BasePluginPlatform.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -40,9 +42,11 @@ public abstract class BasePluginPlatform implements PluginPlatform { protected ServerInformation storeInformation; protected List storeCategories = new ArrayList<>(); - protected List serverEvents = new ArrayList<>(); + protected List serverEvents = new CopyOnWriteArrayList<>(); private final ArrayList PLUGIN_EVENTS = new ArrayList<>(); + private final Set processingCommandIds = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set processingPlayerIds = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** * Checks if the configured store is Geyser/Offline @@ -64,7 +68,7 @@ public final void initStore() { placeholderManager = new PlaceholderManager(); queuedPlayers = Maps.newConcurrentMap(); storeCategories = new ArrayList<>(); - serverEvents = new ArrayList<>(); + serverEvents = new CopyOnWriteArrayList<>(); placeholderManager.register(new UuidPlaceholder(placeholderManager)); if (getPlatformConfig().getSecretKey() != null && !getPlatformConfig().getSecretKey().isEmpty()) { @@ -114,7 +118,6 @@ public final CompletableFuture checkCommandQueue(boolean useRemoteNext } debug("Checking for due players..."); - getQueuedPlayers().clear(); getSDK().getDuePlayers().whenComplete((duePlayersResponse, ex) -> { ArrayList output = new ArrayList<>(); @@ -132,7 +135,7 @@ public final CompletableFuture checkCommandQueue(boolean useRemoteNext output.add("Failed to get due players: '" + ex.getMessage() + "'. We will try again at the next due player check."); executeAsyncLater(this::performCheck, 1, TimeUnit.MINUTES); } - forceCheckOutput.complete((String[]) output.toArray()); + forceCheckOutput.complete(output.toArray(new String[0])); return; } @@ -164,24 +167,33 @@ public final CompletableFuture checkCommandQueue(boolean useRemoteNext public final void handleOnlineCommands(QueuedPlayer player) { if(! isSetup()) return; + if (!processingPlayerIds.add(player.getId())) { + debug("Already processing commands for player '" + player.getName() + "'. Skipping duplicate request."); + return; + } + debug("Processing online commands for player '" + player.getName() + "'..."); Object playerId = getPlayerId(player.getName(), UUIDUtil.mojangIdToJavaId(player.getUuid())); if(!isPlayerOnline(playerId)) { debug("Player " + player.getName() + " has online commands but is not connected. Skipping."); getQueuedPlayers().put(playerId, player.getId()); // will cause commands to be processed when player connects + processingPlayerIds.remove(player.getId()); return; } getSDK().getOnlineCommands(player).thenAccept(onlineCommands -> { if(onlineCommands.isEmpty()) { debug("No commands found for " + player.getName() + "."); + processingPlayerIds.remove(player.getId()); return; } debug("Found " + onlineCommands.size() + " online " + StringUtil.pluralise(onlineCommands.size(), "command") + "."); processOnlineCommands(player.getName(), playerId, onlineCommands); + processingPlayerIds.remove(player.getId()); }).exceptionally(ex -> { warning("Failed to get online commands: " + ex.getMessage(), "We will try again at the next due player check.", ex); + processingPlayerIds.remove(player.getId()); return null; }); } @@ -216,10 +228,15 @@ public final void processOnlineCommands(String playerName, Object playerId, List List completedCommands = new ArrayList<>(); boolean hasInventorySpace = true; for (QueuedCommand command : commands) { + if (!processingCommandIds.add(command.getId())) { + continue; + } + int freeSlots = getFreeSlots(playerId); if(freeSlots < command.getRequiredSlots()) { info(String.format("Skipping command '%s' for player '%s' due to no inventory space. Free slots: %d. Slots required: %d", command.getParsedCommand(), playerName, freeSlots, command.getRequiredSlots())); hasInventorySpace = false; + processingCommandIds.remove(command.getId()); continue; } @@ -287,6 +304,16 @@ public final void handleOfflineCommands() { List completedCommands = new ArrayList<>(); for (QueuedCommand command : offlineData.getCommands()) { + if (!processingCommandIds.add(command.getId())) { + continue; + } + + Object pid = getPlayerId(command.getPlayer().getName(), UUIDUtil.mojangIdToJavaId(command.getPlayer().getUuid())); + if (isPlayerOnline(pid)) { + processingCommandIds.remove(command.getId()); + continue; + } + final Runnable commandRunnable = () -> { info(String.format("Dispatching offline command '%s' for player '%s'.", command.getParsedCommand(), command.getPlayer().getName())); CommandResult offlineCommandResult = dispatchCommand(command.getParsedCommand()); @@ -334,7 +361,11 @@ public final void handleOfflineCommands() { } public final void deleteCompletedCommands(List completedCommands) { - getSDK().deleteCommands(completedCommands).thenRun(completedCommands::clear).exceptionally(ex -> { + List toRemove = new ArrayList<>(completedCommands); + getSDK().deleteCommands(completedCommands).thenRun(() -> { + toRemove.forEach(processingCommandIds::remove); + completedCommands.clear(); + }).exceptionally(ex -> { error("Failed to delete commands: " + ex.getMessage(), ex); return null; }); @@ -616,7 +647,6 @@ public void saveConfig(IPlatformConfig platformConfig) { } } - public void clearSelectedPluginEvents(List events) { serverEvents.removeAll(events); }