From 239993e5b686b75dfc142d4a0b58a8b40cc03e19 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 03:53:01 -0400 Subject: [PATCH 01/11] Initial cohesive Folia reiteration --- pom.xml | 22 +++- .../com/drtshock/playervaults/Metrics.java | 2 +- .../drtshock/playervaults/PlayerVaults.java | 114 ++++++++++-------- .../playervaults/commands/ConvertCommand.java | 4 +- .../playervaults/commands/HelpMeCommand.java | 92 +++++++------- .../playervaults/listeners/Listeners.java | 2 +- .../listeners/VaultPreloadListener.java | 8 +- .../vaultmanagement/EconomyOperations.java | 2 +- .../vaultmanagement/VaultManager.java | 42 +++---- .../vaultmanagement/VaultOperations.java | 30 +++-- src/main/resources/plugin.yml | 1 + 11 files changed, 166 insertions(+), 153 deletions(-) diff --git a/pom.xml b/pom.xml index 55defa7..ab86dd4 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ maven-compiler-plugin - 3.13.0 + 3.14.0 21 @@ -56,6 +56,10 @@ net.kyori com.drtshock.playervaults.lib.net.kyori + + com.tcoded.folialib + com.drtshock.playervaults.lib.folialib + org.kitteh com.drtshock.playervaults.lib.org.kitteh @@ -95,6 +99,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ + + tcoded-releases + https://repo.tcoded.com/releases + placeholderapi https://repo.extendedclip.com/content/repositories/placeholderapi/ @@ -119,7 +127,7 @@ net.kyori adventure-text-serializer-gson - 4.23.0 + 4.24.0 compile true @@ -144,7 +152,7 @@ net.kyori adventure-text-serializer-legacy - 4.23.0 + 4.24.0 compile true @@ -161,7 +169,7 @@ net.kyori adventure-text-minimessage - 4.23.0 + 4.24.0 compile true @@ -193,6 +201,12 @@ 1.21.6-R0.1-SNAPSHOT provided + + com.tcoded + FoliaLib + 0.5.1 + compile + com.googlecode.json-simple json-simple diff --git a/src/main/java/com/drtshock/playervaults/Metrics.java b/src/main/java/com/drtshock/playervaults/Metrics.java index caf9f92..7ef5b32 100644 --- a/src/main/java/com/drtshock/playervaults/Metrics.java +++ b/src/main/java/com/drtshock/playervaults/Metrics.java @@ -190,7 +190,7 @@ public void run() { } // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); + PlayerVaults.scheduler().runNextTick(task -> submitData()); } }, 1000 * 60 * 5, 1000 * 60 * 30); // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index cace052..a79f199 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -39,7 +39,8 @@ import com.drtshock.playervaults.vaultmanagement.VaultManager; import com.drtshock.playervaults.vaultmanagement.VaultViewInfo; import com.google.gson.Gson; -import net.kyori.adventure.audience.Audience; +import com.tcoded.folialib.FoliaLib; +import com.tcoded.folialib.impl.PlatformScheduler; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -59,7 +60,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; import dev.kitteh.cardboardbox.CardboardBox; import sun.misc.Unsafe; @@ -87,6 +87,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Supplier; import java.util.logging.Level; @@ -96,13 +97,15 @@ public class PlayerVaults extends JavaPlugin { public static boolean DEBUG; private static PlayerVaults instance; - private final HashMap setSign = new HashMap<>(); + private static FoliaLib foliaLib; + private static PlatformScheduler scheduler; + private final ConcurrentHashMap setSign = new ConcurrentHashMap<>(); // Player name - VaultViewInfo - private final HashMap inVault = new HashMap<>(); + private final ConcurrentHashMap inVault = new ConcurrentHashMap<>(); // VaultViewInfo - Inventory - private final HashMap openInventories = new HashMap<>(); - private final Set blockedMats = new HashSet<>(); - private final Set blockedEnchs = new HashSet<>(); + private final ConcurrentHashMap openInventories = new ConcurrentHashMap<>(); + private final Set blockedMats = ConcurrentHashMap.newKeySet(); + private final Set blockedEnchs = ConcurrentHashMap.newKeySet(); private boolean blockWithModelData = false; private boolean blockWithoutModelData = false; private boolean useVault; @@ -126,6 +129,14 @@ public static PlayerVaults getInstance() { return instance; } + public static FoliaLib foliaLib() { + return foliaLib; + } + + public static PlatformScheduler scheduler() { + return scheduler; + } + public static void debug(String s, long start) { if (DEBUG) { instance.getLogger().log(Level.INFO, "{0} took {1}ms", new Object[]{s, (System.currentTimeMillis() - start)}); @@ -146,6 +157,8 @@ public void onEnable() { return; } instance = this; + foliaLib = new FoliaLib(this); + scheduler = foliaLib.getScheduler(); long start = System.currentTimeMillis(); long time = System.currentTimeMillis(); UpdateCheck update = new UpdateCheck("PlayerVaultsX", this.getDescription().getVersion(), this.getServer().getName(), this.getServer().getVersion()); @@ -186,17 +199,15 @@ public void onEnable() { debug("setup economy", time); if (getConf().getPurge().isEnabled()) { - getServer().getScheduler().runTaskAsynchronously(this, new Cleanup(getConf().getPurge().getDaysSinceLastEdit())); + final int days = getConf().getPurge().getDaysSinceLastEdit(); + PlayerVaults.scheduler().runLaterAsync(new Cleanup(days), 1L); } - new BukkitRunnable() { - @Override - public void run() { - if (saveQueued) { - saveSignsFile(); - } + PlayerVaults.scheduler().runTimer(() -> { + if (saveQueued) { + saveSignsFile(); } - }.runTaskTimer(this, 20, 20); + }, 20, 20); this.metrics = new Metrics(this, 6905); Plugin vault = getServer().getPluginManager().getPlugin("Vault"); @@ -296,42 +307,39 @@ public void run() { this.updateCheck = new Gson().toJson(update); if (!HelpMeCommand.likesCats) return; - new BukkitRunnable() { - @Override - public void run() { - try { - URL url = new URL("https://update.plugin.party/check"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("POST"); - con.setDoOutput(true); - con.setRequestProperty("Content-Type", "application/json"); - con.setRequestProperty("Accept", "application/json"); - try (OutputStream out = con.getOutputStream()) { - out.write(PlayerVaults.this.updateCheck.getBytes(StandardCharsets.UTF_8)); - } - String reply = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); - Response response = new Gson().fromJson(reply, Response.class); - if (response.isSuccess()) { - if (response.isUpdateAvailable()) { - PlayerVaults.this.updateResponse = response; - if (response.isUrgent()) { - PlayerVaults.this.getServer().getOnlinePlayers().forEach(PlayerVaults.this::updateNotification); - } - PlayerVaults.this.getLogger().warning("Update available: " + response.getLatestVersion() + (response.getMessage() == null ? "" : (" - " + response.getMessage()))); + PlayerVaults.scheduler().runTimerAsync(task -> { + try { + URL url = new URL("https://update.plugin.party/check"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setDoOutput(true); + con.setRequestProperty("Content-Type", "application/json"); + con.setRequestProperty("Accept", "application/json"); + try (OutputStream out = con.getOutputStream()) { + out.write(PlayerVaults.this.updateCheck.getBytes(StandardCharsets.UTF_8)); + } + String reply = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); + Response response = new Gson().fromJson(reply, Response.class); + if (response.isSuccess()) { + if (response.isUpdateAvailable()) { + PlayerVaults.this.updateResponse = response; + if (response.isUrgent()) { + PlayerVaults.this.getServer().getOnlinePlayers().forEach(PlayerVaults.this::updateNotification); } + PlayerVaults.this.getLogger().warning("Update available: " + response.getLatestVersion() + (response.getMessage() == null ? "" : (" - " + response.getMessage()))); + } + } else { + if (response.getMessage().equals("INVALID")) { + task.cancel(); + } else if (response.getMessage().equals("TOO_FAST")) { + // Nothing for now } else { - if (response.getMessage().equals("INVALID")) { - this.cancel(); - } else if (response.getMessage().equals("TOO_FAST")) { - // Nothing for now - } else { - PlayerVaults.this.getLogger().warning("Failed to check for updates: " + response.getMessage()); - } + PlayerVaults.this.getLogger().warning("Failed to check for updates: " + response.getMessage()); } - } catch (Exception ignored) { } + } catch (Exception ignored) { } - }.runTaskTimerAsynchronously(this, 1, 20 /* ticks */ * 60 /* seconds in a minute */ * 60 /* minutes in an hour*/); + }, 1, 20 /* ticks */ * 60 /* seconds in a minute */ * 60 /* minutes in an hour*/); } private void metricsLine(String name, Callable callable) { @@ -368,12 +376,12 @@ public void onDisable() { VaultManager.getInstance().saveVault(inventory, player.getUniqueId().toString(), info.getNumber()); this.openInventories.remove(info.toString()); // try this to make sure that they can't make further edits if the process hangs. - player.closeInventory(); + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); } this.inVault.remove(player.getUniqueId().toString()); debug("Closing vault for " + player.getName()); - player.closeInventory(); + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); } } @@ -542,15 +550,15 @@ private void saveSignsFile() { } } - public HashMap getSetSign() { + public ConcurrentHashMap getSetSign() { return this.setSign; } - public HashMap getInVault() { + public ConcurrentHashMap getInVault() { return this.inVault; } - public HashMap getOpenInventories() { + public ConcurrentHashMap getOpenInventories() { return this.openInventories; } @@ -731,7 +739,7 @@ public Component getComponent() { } } - private final Set told = new HashSet<>(); + private final Set told = ConcurrentHashMap.newKeySet(); public void updateNotification(Player player) { if (updateResponse == null || !player.hasPermission(Permission.ADMIN)) { @@ -759,7 +767,7 @@ public T addException(T t) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); t.printStackTrace(printWriter); - builder.append(stringWriter.toString()); + builder.append(stringWriter); this.exceptions.add(builder.toString()); } return t; diff --git a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java index d6e0943..cf5bd72 100644 --- a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java @@ -71,7 +71,7 @@ public boolean onCommand(final CommandSender sender, Command command, String lab } else { // Fork into background this.plugin.getTL().convertBackground().title().send(sender); - PlayerVaults.getInstance().getServer().getScheduler().runTaskLaterAsynchronously(PlayerVaults.getInstance(), () -> { + PlayerVaults.scheduler().runLaterAsync(() -> { int converted = 0; VaultOperations.setLocked(true); for (Converter converter : applicableConverters) { @@ -87,4 +87,4 @@ public boolean onCommand(final CommandSender sender, Command command, String lab } return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java b/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java index 9e0f2fc..fce829f 100644 --- a/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/HelpMeCommand.java @@ -30,7 +30,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; import org.kitteh.pastegg.PasteBuilder; import org.kitteh.pastegg.PasteContent; import org.kitteh.pastegg.PasteFile; @@ -43,6 +42,8 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Arrays; +import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.logging.Level; public class HelpMeCommand implements CommandExecutor { @@ -77,61 +78,52 @@ public boolean onCommand(final CommandSender sender, Command command, String lab mainInfo.append(" ").append(plugin.getDescription().getAuthors()).append('\n'); } - new BukkitRunnable() { - private final PasteBuilder builder = new PasteBuilder().name("PlayerVaultsX Debug") - .visibility(Visibility.UNLISTED) - .expires(ZonedDateTime.now(ZoneOffset.UTC).plusDays(3)); - private int i = 0; + PlayerVaults.scheduler().runAsync(task -> { + try { + final PasteBuilder builder = new PasteBuilder().name("PlayerVaultsX Debug") + .visibility(Visibility.UNLISTED) + .expires(ZonedDateTime.now(ZoneOffset.UTC).plusDays(3)); + final int[] i = new int[]{0}; - private void add(String name, String content) { - builder.addFile(new PasteFile(i++ + name, new PasteContent(PasteContent.ContentType.TEXT, content))); - } + final BiConsumer add = (name, content) -> + builder.addFile(new PasteFile(i[0]++ + name, new PasteContent(PasteContent.ContentType.TEXT, content))); + final Function getFile = (file) -> { + try { + return new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + } catch (IOException e) { + return e.getMessage(); + } + }; - private String getFile(Path file) { - try { - return new String(Files.readAllBytes(file), StandardCharsets.UTF_8); - } catch (IOException e) { - return e.getMessage(); + Path dataPath = plugin.getDataFolder().toPath(); + add.accept("info.txt", mainInfo.toString()); + String exceptionLog = plugin.getExceptions(); + if (exceptionLog != null) { + add.accept("exceptions.txt", exceptionLog); } - } + add.accept("config.conf", getFile.apply(dataPath.resolve("config.conf"))); - @Override - public void run() { - try { - Path dataPath = plugin.getDataFolder().toPath(); - add("info.txt", mainInfo.toString()); - String exceptionLog = plugin.getExceptions(); - if (exceptionLog != null) { - add("exceptions.txt", exceptionLog); + PasteBuilder.PasteResult result = builder.build(); + + PlayerVaults.scheduler().runNextTick(t -> { + if (result.getPaste().isPresent()) { + String delKey = result.getPaste().get().getDeletionKey().orElse("No deletion key"); + String url = "https://paste.gg/anonymous/" + result.getPaste().get().getId(); + ComponentDispatcher.send(sender, Component.text("URL generated: ").append(Component.text().clickEvent(ClickEvent.openUrl(url)).content(url))); + ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize((sender instanceof Player ? "" : "") + "Deletion key: " + delKey)); + } else { + ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); + PlayerVaults.getInstance().getLogger().warning("Received: " + result.getMessage()); } - add("config.conf", getFile(dataPath.resolve("config.conf"))); - PasteBuilder.PasteResult result = builder.build(); - new BukkitRunnable() { - @Override - public void run() { - if (result.getPaste().isPresent()) { - String delKey = result.getPaste().get().getDeletionKey().orElse("No deletion key"); - String url = "https://paste.gg/anonymous/" + result.getPaste().get().getId(); - ComponentDispatcher.send(sender, Component.text("URL generated: ").append(Component.text().clickEvent(ClickEvent.openUrl(url)).content(url))); - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize((sender instanceof Player ? "" : "") + "Deletion key: " + delKey)); - } else { - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); - PlayerVaults.getInstance().getLogger().warning("Received: " + result.getMessage()); - } - } - }.runTask(PlayerVaults.getInstance()); - } catch (Exception e) { - PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to execute debug command", e); - new BukkitRunnable() { - @Override - public void run() { - ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")); - } - }.runTask(PlayerVaults.getInstance()); - } + }); + } catch (Exception e) { + PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to execute debug command", e); + PlayerVaults.scheduler().runNextTick(t -> + ComponentDispatcher.send(sender, MiniMessage.miniMessage().deserialize("Failed to generate output. See console for details.")) + ); } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + }); } return true; } -} \ No newline at end of file +} diff --git a/src/main/java/com/drtshock/playervaults/listeners/Listeners.java b/src/main/java/com/drtshock/playervaults/listeners/Listeners.java index 5e60897..1165a3b 100644 --- a/src/main/java/com/drtshock/playervaults/listeners/Listeners.java +++ b/src/main/java/com/drtshock/playervaults/listeners/Listeners.java @@ -227,4 +227,4 @@ private boolean isBlocked(Player player, ItemStack item, VaultViewInfo info) { } return false; } -} \ No newline at end of file +} diff --git a/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java b/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java index 14467a0..d1136f8 100644 --- a/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java +++ b/src/main/java/com/drtshock/playervaults/listeners/VaultPreloadListener.java @@ -25,7 +25,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; import java.util.UUID; @@ -37,12 +36,7 @@ public class VaultPreloadListener implements Listener { public void onPlayerJoin(PlayerJoinEvent event) { PlayerVaults.getInstance().updateNotification(event.getPlayer()); final UUID uuid = event.getPlayer().getUniqueId(); - new BukkitRunnable() { - @Override - public void run() { - vm.cachePlayerVaultFile(uuid.toString()); - } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + PlayerVaults.scheduler().runAsync(task -> vm.cachePlayerVaultFile(uuid.toString())); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java index 8418ada..5f1b1e0 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/EconomyOperations.java @@ -123,7 +123,7 @@ public static boolean refundOnDelete(Player player, int number) { return true; } - File playerFile = new File(PlayerVaults.getInstance().getVaultData(), player.getUniqueId().toString() + ".yml"); + File playerFile = new File(PlayerVaults.getInstance().getVaultData(), player.getUniqueId() + ".yml"); if (playerFile.exists()) { YamlConfiguration playerData = YamlConfiguration.loadConfiguration(playerFile); if (playerData.getString("vault" + number) == null) { diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 742ac8c..6cf58e8 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -27,7 +27,6 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.IOException; @@ -69,7 +68,7 @@ public static VaultManager getInstance() { */ public void saveVault(Inventory inventory, String target, int number) { YamlConfiguration yaml = getPlayerVaultFile(target, true); - int size = VaultOperations.getMaxVaultSize(target); + VaultOperations.getMaxVaultSize(target); String serialized = CardboardBoxSerialization.toStorage(inventory, target); yaml.set(String.format(VAULTKEY, number), serialized); saveFileSync(target, yaml); @@ -241,7 +240,6 @@ public Set getVaultNumbers(String holder) { } } - return vaults; } @@ -258,27 +256,24 @@ public void deleteAllVaults(String holder) { * @param number The vault number. */ public void deleteVault(CommandSender sender, final String holder, final int number) { - new BukkitRunnable() { - @Override - public void run() { - File file = new File(directory, holder + ".yml"); - if (!file.exists()) { - return; - } + PlayerVaults.scheduler().runAsync(task -> { + File file = new File(directory, holder + ".yml"); + if (!file.exists()) { + return; + } - YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); - if (file.exists()) { - playerFile.set(String.format(VAULTKEY, number), null); - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, playerFile); - } - try { - playerFile.save(file); - } catch (IOException ignored) { - } + YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); + if (file.exists()) { + playerFile.set(String.format(VAULTKEY, number), null); + if (cachedVaultFiles.containsKey(holder)) { + cachedVaultFiles.put(holder, playerFile); + } + try { + playerFile.save(file); + } catch (IOException ignored) { } } - }.runTaskAsynchronously(PlayerVaults.getInstance()); + }); OfflinePlayer player = Bukkit.getPlayer(holder); if (player != null) { @@ -312,10 +307,7 @@ public void removeCachedPlayerVaultFile(String holder) { * @return The holder's vault config file. */ public YamlConfiguration getPlayerVaultFile(String holder, boolean createIfNotFound) { - if (cachedVaultFiles.containsKey(holder)) { - return cachedVaultFiles.get(holder); - } - return loadPlayerVaultFile(holder, createIfNotFound); + return cachedVaultFiles.computeIfAbsent(holder, key -> loadPlayerVaultFile(key, createIfNotFound)); } public YamlConfiguration loadPlayerVaultFile(String holder) { diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index 9134795..c37bcfa 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -62,7 +62,7 @@ public static void setLocked(boolean locked) { if (player.getOpenInventory() != null) { InventoryView view = player.getOpenInventory(); if (view.getTopInventory().getHolder() instanceof VaultHolder) { - player.closeInventory(); + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); PlayerVaults.getInstance().getTL().locked().title().send(player); } } @@ -156,23 +156,35 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo if (checkPerms(player, number)) { if (free || EconomyOperations.payToOpen(player, number)) { - Inventory inv = VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)); + VaultViewInfo info = new VaultViewInfo(player.getUniqueId().toString(), number); + + final Inventory inv = PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), (key) -> { + Inventory loadedVault = VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)); + if (loadedVault == null) { + PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); + } + return loadedVault; + }); + if (inv == null) { PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); return false; } - player.openInventory(inv); + if (PlayerVaults.getInstance().getOpenInventories().get(info.toString()) != inv) { + PlayerVaults.debug("Vault for " + player.getName() + " was already opened by another thread."); + return true; + } + + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); // Check if the inventory was actually opened if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); return false; // inventory open event was cancelled. } - VaultViewInfo info = new VaultViewInfo(player.getUniqueId().toString(), number); - PlayerVaults.getInstance().getOpenInventories().put(info.toString(), inv); - if (send) { PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); } @@ -249,7 +261,7 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar if (inv == null) { PlayerVaults.getInstance().getTL().vaultDoesNotExist().title().send(player); } else { - player.openInventory(inv); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); // Check if the inventory was actually opened if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { @@ -369,7 +381,7 @@ private static boolean isNumber(String check) { private record PlayerCount(int count, Instant time) { } - private static Map countCache = new ConcurrentHashMap<>(); + private static final Map countCache = new ConcurrentHashMap<>(); private static final int secondsToLive = 2; @@ -387,7 +399,7 @@ public static int countVaults(Player player) { } PlayerCount newCount = new PlayerCount(vaultCount, Instant.now()); countCache.put(uuid, newCount); - PlayerVaults.getInstance().getServer().getScheduler().runTaskLater(PlayerVaults.getInstance(), () -> { + PlayerVaults.scheduler().runLater(() -> { if (countCache.get(uuid) == newCount) { countCache.remove(uuid); // Do a lil cleanup to avoid the world's smallest memory leak } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index eecddd0..7948698 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ version: ${project.version} main: com.drtshock.playervaults.PlayerVaults softdepend: [Vault, Multiverse-Inventories, PlaceholderAPI] api-version: 1.21.6 +folia-supported: true commands: pv: From a260be61d407458befde40db685bf12803e11cee Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 04:26:16 -0400 Subject: [PATCH 02/11] Comprehensive safety checks to match explicit requirements (experimental) --- .../drtshock/playervaults/PlayerVaults.java | 4 +- .../vaultmanagement/VaultManager.java | 221 ++++++++++---- .../vaultmanagement/VaultOperations.java | 270 +++++++++++------- .../vaultmanagement/VaultViewInfo.java | 3 +- 4 files changed, 331 insertions(+), 167 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index a79f199..26c677a 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -41,6 +41,7 @@ import com.google.gson.Gson; import com.tcoded.folialib.FoliaLib; import com.tcoded.folialib.impl.PlatformScheduler; +import dev.kitteh.cardboardbox.CardboardBox; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -60,7 +61,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import dev.kitteh.cardboardbox.CardboardBox; import sun.misc.Unsafe; import java.io.BufferedReader; @@ -373,7 +373,7 @@ public void onDisable() { Inventory inventory = player.getOpenInventory().getTopInventory(); if (inventory.getViewers().size() == 1) { VaultViewInfo info = this.inVault.get(player.getUniqueId().toString()); - VaultManager.getInstance().saveVault(inventory, player.getUniqueId().toString(), info.getNumber()); + VaultManager.getInstance().saveVault(inventory, info.getVaultName(), info.getNumber()); this.openInventories.remove(info.toString()); // try this to make sure that they can't make further edits if the process hangs. PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 6cf58e8..8e99dd2 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -30,13 +30,18 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import static com.drtshock.playervaults.vaultmanagement.VaultOperations.VaultGate; + public class VaultManager { private static final String VAULTKEY = "vault%d"; @@ -59,6 +64,33 @@ public static VaultManager getInstance() { return instance; } + /** + * Resolve a stable, UUID-first key for a holder. + */ + public static String normalizeHolderKey(String input) { + if (input == null) { + return null; + } + + File dataDir = PlayerVaults.getInstance().getVaultData(); + File legacy = new File(dataDir, input + ".yml"); + if (legacy.exists()) { + return input; + } + + try { + UUID uuid = UUID.fromString(input); + return uuid.toString(); + } catch (Exception ignored) {} + + OfflinePlayer byName = Bukkit.getOfflinePlayer(input); + if (byName != null && byName.getUniqueId() != null) { + return byName.getUniqueId().toString(); + } + + return input; + } + /** * Saves the inventory to the specified player and vault number. * @@ -67,11 +99,14 @@ public static VaultManager getInstance() { * @param number The vault number. */ public void saveVault(Inventory inventory, String target, int number) { - YamlConfiguration yaml = getPlayerVaultFile(target, true); - VaultOperations.getMaxVaultSize(target); - String serialized = CardboardBoxSerialization.toStorage(inventory, target); - yaml.set(String.format(VAULTKEY, number), serialized); - saveFileSync(target, yaml); + final String holderKey = normalizeHolderKey(target); + VaultGate.withLock(new VaultGate.VaultKey(holderKey, number), () -> { + YamlConfiguration yaml = getPlayerVaultFile(holderKey, true); + VaultOperations.getMaxVaultSize(holderKey); + String serialized = CardboardBoxSerialization.toStorage(inventory, holderKey); + yaml.set(String.format(VAULTKEY, number), serialized); + saveFileSync(holderKey, yaml); + }); } /** @@ -117,28 +152,19 @@ public Inventory loadOtherVault(String name, int number, int size) { size = PlayerVaults.getInstance().getDefaultVaultSize(); } - PlayerVaults.debug("Loading other vault for " + name); - - String holder = name; - - try { - UUID uuid = UUID.fromString(name); - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); - holder = offlinePlayer.getUniqueId().toString(); - } catch (Exception e) { - // Not a player - } + final String holderKey = normalizeHolderKey(name); + PlayerVaults.debug("Loading other vault for " + holderKey); String title = PlayerVaults.getInstance().getVaultTitle(String.valueOf(number)); - VaultViewInfo info = new VaultViewInfo(name, number); + VaultViewInfo info = new VaultViewInfo(holderKey, number); Inventory inv; VaultHolder vaultHolder = new VaultHolder(number); if (PlayerVaults.getInstance().getOpenInventories().containsKey(info.toString())) { PlayerVaults.debug("Already open"); inv = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); } else { - YamlConfiguration playerFile = getPlayerVaultFile(holder, true); - Inventory i = getInventory(vaultHolder, holder, playerFile, size, number, title); + YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); + Inventory i = getInventory(vaultHolder, holderKey, playerFile, size, number, title); if (i == null) { return null; } else { @@ -171,7 +197,7 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf // Happens on change of permission or if people used the broken version. // In this case, players will lose items. if (deserialized.length > size) { - PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size+". Attempting to rescue!"); + PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size + ". Attempting to rescue!"); for (ItemStack stack : deserialized) { if (stack != null) { inventory.addItem(stack); @@ -193,11 +219,14 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf * @return The inventory of the specified holder and vault number. Can be null. */ public Inventory getVault(String holder, int number) { - YamlConfiguration playerFile = getPlayerVaultFile(holder, true); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); String serialized = playerFile.getString(String.format(VAULTKEY, number)); - ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holder); - Inventory inventory = Bukkit.createInventory(null, contents.length, holder + " vault " + number); - inventory.setContents(contents); + ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holderKey); + int size = Math.max(9, ((contents.length + 8) / 9) * 9); + Inventory inventory = Bukkit.createInventory(null, size, holderKey + " vault " + number); + ItemStack[] copy = Arrays.copyOf(contents, size); + inventory.setContents(copy); return inventory; } @@ -209,12 +238,13 @@ public Inventory getVault(String holder, int number) { * @return true if the vault file and vault number exist in that file, otherwise false. */ public boolean vaultExists(String holder, int number) { - File file = new File(directory, holder + ".yml"); + String holderKey = normalizeHolderKey(holder); + File file = new File(directory, holderKey + ".yml"); if (!file.exists()) { return false; } - return getPlayerVaultFile(holder, true).contains(String.format(VAULTKEY, number)); + return getPlayerVaultFile(holderKey, true).contains(String.format(VAULTKEY, number)); } /** @@ -225,7 +255,8 @@ public boolean vaultExists(String holder, int number) { */ public Set getVaultNumbers(String holder) { Set vaults = new HashSet<>(); - YamlConfiguration file = getPlayerVaultFile(holder, true); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration file = getPlayerVaultFile(holderKey, true); if (file == null) { return vaults; } @@ -244,8 +275,28 @@ public Set getVaultNumbers(String holder) { } public void deleteAllVaults(String holder) { - removeCachedPlayerVaultFile(holder); - deletePlayerVaultFile(holder); + String holderKey = normalizeHolderKey(holder); + removeCachedPlayerVaultFile(holderKey); + deletePlayerVaultFile(holderKey); + + List toRemove = new ArrayList<>(); + PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { + if (holderKey.equals(info.getVaultName())) { + toRemove.add(viewerId); + Player p = null; + try { + p = Bukkit.getPlayer(UUID.fromString(viewerId)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (p != null) { + Player finalP = p; + PlayerVaults.scheduler().runAtEntity(finalP, task -> finalP.closeInventory()); + } + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); + } + }); + toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); } /** @@ -256,48 +307,82 @@ public void deleteAllVaults(String holder) { * @param number The vault number. */ public void deleteVault(CommandSender sender, final String holder, final int number) { - PlayerVaults.scheduler().runAsync(task -> { - File file = new File(directory, holder + ".yml"); - if (!file.exists()) { - return; - } + final String holderKey = normalizeHolderKey(holder); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); + + PlayerVaults.scheduler().runAsync(task -> + VaultGate.withLock(gateKey, () -> { + File file = new File(directory, holderKey + ".yml"); + if (!file.exists()) { + return; + } - YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); - if (file.exists()) { + YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); playerFile.set(String.format(VAULTKEY, number), null); - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, playerFile); - } + cachedVaultFiles.put(holderKey, playerFile); try { playerFile.save(file); } catch (IOException ignored) { } - } - }); - OfflinePlayer player = Bukkit.getPlayer(holder); - if (player != null) { - if (sender.getName().equalsIgnoreCase(player.getName())) { + String key = new VaultViewInfo(holderKey, number).toString(); + PlayerVaults.getInstance().getOpenInventories().remove(key); + + List toRemove = new ArrayList<>(); + PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { + if (holderKey.equals(info.getVaultName()) && info.getNumber() == number) { + toRemove.add(viewerId); + Player p = null; + try { + p = Bukkit.getPlayer(UUID.fromString(viewerId)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (p != null) { + Player finalP = p; + PlayerVaults.scheduler().runAtEntity(finalP, t -> finalP.closeInventory()); + } + } + }); + toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); + }) + ); + + OfflinePlayer target = null; + try { + target = Bukkit.getOfflinePlayer(UUID.fromString(holderKey)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (target != null && target.getName() != null) { + if (sender.getName().equalsIgnoreCase(target.getName())) { this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); } else { - this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", player.getName()).send(sender); + this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", target.getName()).send(sender); + } + } else { + if (sender.getName().equalsIgnoreCase(holder)) { + this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); + } else { + this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", holder).send(sender); } } - String vaultName = sender instanceof Player ? ((Player) sender).getUniqueId().toString() : holder; - PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(vaultName, number).toString()); + PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(holderKey, number).toString()); } // Should only be run asynchronously public void cachePlayerVaultFile(String holder) { - YamlConfiguration config = this.loadPlayerVaultFile(holder, false); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration config = this.loadPlayerVaultFile(holderKey, false); if (config != null) { - this.cachedVaultFiles.put(holder, config); + this.cachedVaultFiles.put(holderKey, config); } } public void removeCachedPlayerVaultFile(String holder) { - cachedVaultFiles.remove(holder); + String holderKey = normalizeHolderKey(holder); + cachedVaultFiles.remove(holderKey); } /** @@ -307,7 +392,8 @@ public void removeCachedPlayerVaultFile(String holder) { * @return The holder's vault config file. */ public YamlConfiguration getPlayerVaultFile(String holder, boolean createIfNotFound) { - return cachedVaultFiles.computeIfAbsent(holder, key -> loadPlayerVaultFile(key, createIfNotFound)); + String holderKey = resolveFileKey(holder); + return cachedVaultFiles.computeIfAbsent(holderKey, key -> loadPlayerVaultFile(key, createIfNotFound)); } public YamlConfiguration loadPlayerVaultFile(String holder) { @@ -320,7 +406,8 @@ public YamlConfiguration loadPlayerVaultFile(String holder) { * @param holder UUID of the holder. */ public void deletePlayerVaultFile(String holder) { - File file = new File(this.directory, holder + ".yml"); + String holderKey = resolveFileKey(holder); + File file = new File(this.directory, holderKey + ".yml"); if (file.exists()) { file.delete(); } @@ -348,22 +435,38 @@ public YamlConfiguration loadPlayerVaultFile(String uniqueId, boolean createIfNo } public void saveFileSync(final String holder, final YamlConfiguration yaml) { - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, yaml); + String holderKey = resolveFileKey(holder); + if (cachedVaultFiles.containsKey(holderKey)) { + cachedVaultFiles.put(holderKey, yaml); } final boolean backups = PlayerVaults.getInstance().isBackupsEnabled(); final File backupsFolder = PlayerVaults.getInstance().getBackupsFolder(); - final File file = new File(directory, holder + ".yml"); + final File file = new File(directory, holderKey + ".yml"); if (file.exists() && backups) { - file.renameTo(new File(backupsFolder, holder + ".yml")); + file.renameTo(new File(backupsFolder, holderKey + ".yml")); } + try { yaml.save(file); } catch (IOException e) { - PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holder, e)); - PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holder, e); + PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holderKey, e)); + PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holderKey, e); + } + + PlayerVaults.debug("Saved vault for " + holderKey); + } + + private String resolveFileKey(String holder) { + if (holder == null) { + return null; } - PlayerVaults.debug("Saved vault for " + holder); + + File legacy = new File(this.directory, holder + ".yml"); + if (legacy.exists()) { + return holder; + } + + return normalizeHolderKey(holder); } } diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index c37bcfa..cebe69f 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -34,11 +34,48 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; public class VaultOperations { private static final AtomicBoolean LOCKED = new AtomicBoolean(false); + public static final class VaultGate { + + private static final ConcurrentHashMap LOCKS = new ConcurrentHashMap<>(); + + public static T withLock(VaultKey key, Supplier body) { + ReentrantLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantLock()); + lock.lock(); + try { + return body.get(); + } finally { + try { + lock.unlock(); + } finally { + if (!lock.hasQueuedThreads()) { + LOCKS.remove(key, lock); + } + } + } + } + + public static void withLock(VaultKey key, Runnable body) { + withLock(key, () -> { + body.run(); + return null; + }); + } + + public record VaultKey(String ownerKey, int number) { + @Override + public String toString() { + return ownerKey + " " + number; + } + } + } + /** * Gets whether or not player vaults are locked * @@ -59,12 +96,10 @@ public static void setLocked(boolean locked) { if (locked) { for (Player player : PlayerVaults.getInstance().getServer().getOnlinePlayers()) { - if (player.getOpenInventory() != null) { - InventoryView view = player.getOpenInventory(); - if (view.getTopInventory().getHolder() instanceof VaultHolder) { - PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); - PlayerVaults.getInstance().getTL().locked().title().send(player); - } + InventoryView view = player.getOpenInventory(); + if (view != null && view.getTopInventory() != null && view.getTopInventory().getHolder() instanceof VaultHolder) { + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); + PlayerVaults.getInstance().getTL().locked().title().send(player); } } } @@ -143,7 +178,7 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo if (player.isSleeping() || player.isDead() || !player.isOnline()) { return false; } - int number; + final int number; try { number = Integer.parseInt(arg); if (number < 1) { @@ -154,49 +189,44 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo return false; } - if (checkPerms(player, number)) { - if (free || EconomyOperations.payToOpen(player, number)) { - VaultViewInfo info = new VaultViewInfo(player.getUniqueId().toString(), number); + if (!checkPerms(player, number)) { + PlayerVaults.getInstance().getTL().noPerms().title().send(player); + return false; + } - final Inventory inv = PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), (key) -> { - Inventory loadedVault = VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)); - if (loadedVault == null) { - PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); - } - return loadedVault; - }); + if (!free && !EconomyOperations.payToOpen(player, number)) { + PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); + return false; + } - if (inv == null) { - PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); - return false; - } + final String ownerKey = player.getUniqueId().toString(); + final VaultViewInfo info = new VaultViewInfo(ownerKey, number); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(ownerKey, number); - if (PlayerVaults.getInstance().getOpenInventories().get(info.toString()) != inv) { - PlayerVaults.debug("Vault for " + player.getName() + " was already opened by another thread."); - return true; - } + Inventory inv = VaultGate.withLock(gateKey, () -> + PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), key -> + VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)) + ) + ); - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + if (inv == null) { + PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); + return false; + } - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); - return false; // inventory open event was cancelled. - } + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - if (send) { - PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); - } - return true; - } else { - PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); - return false; - } - } else { - PlayerVaults.getInstance().getTL().noPerms().title().send(player); + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); + return false; // inventory open event was cancelled. } - return false; + + if (send) { + PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); + } + return true; } /** @@ -219,7 +249,7 @@ public static boolean openOwnVault(Player player, String arg, boolean isCommand) * Open another player's vault. * * @param player The player to open to. - * @param vaultOwner The name of the vault owner. + * @param vaultOwner The name or UUID of the vault owner. * @param arg The vault number to open. * @return Whether or not the player was allowed to open it. */ @@ -236,9 +266,7 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar return false; } - long time = System.currentTimeMillis(); - - int number = 0; + final int number; try { number = Integer.parseInt(arg); if (number < 1) { @@ -247,41 +275,61 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar } } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return false; } - Inventory inv = VaultManager.getInstance().loadOtherVault(vaultOwner, number, getMaxVaultSize(vaultOwner)); - String name = vaultOwner; + final String holderKey = VaultManager.normalizeHolderKey(vaultOwner); + final VaultViewInfo info = new VaultViewInfo(holderKey, number); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); + + long time = System.currentTimeMillis(); + + Inventory inv = VaultGate.withLock(gateKey, () -> { + Inventory cached = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); + if (cached != null) { + return cached; + } + Inventory loaded = VaultManager.getInstance().loadOtherVault(holderKey, number, getMaxVaultSize(holderKey)); + if (loaded != null) { + PlayerVaults.getInstance().getOpenInventories().put(info.toString(), loaded); + } + return loaded; + }); + + // Resolve a nice display name if possible + String displayName = holderKey; try { - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(UUID.fromString(vaultOwner)); - name = offlinePlayer.getName(); + UUID uuid = UUID.fromString(holderKey); + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + if (op.getName() != null) { + displayName = op.getName(); + } } catch (Exception e) { // not a player } if (inv == null) { PlayerVaults.getInstance().getTL().vaultDoesNotExist().title().send(player); - } else { - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + PlayerVaults.debug("Opening other vault that does not fully exist.", time); + return false; + } - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - return false; // inventory open event was cancelled. - } - if (send) { - PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", name).send(player); - } - PlayerVaults.debug("opening other vault", time); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - // Need to set ViewInfo for a third party vault for the opening player. - VaultViewInfo info = new VaultViewInfo(vaultOwner, number); - PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); - PlayerVaults.getInstance().getOpenInventories().put(player.getUniqueId().toString(), inv); - return true; + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + return false; // inventory open event was cancelled. } - PlayerVaults.debug("opening other vault returning false", time); - return false; + if (send) { + PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", displayName).send(player); + } + PlayerVaults.debug("opening other vault", time); + + // Track which vault this viewer is in + PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); + return true; } /** @@ -294,25 +342,27 @@ public static void deleteOwnVault(Player player, String arg) { if (isLocked()) { return; } - if (isNumber(arg)) { - int number = 0; - try { - number = Integer.parseInt(arg); - if (number == 0) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return; - } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - } - if (EconomyOperations.refundOnDelete(player, number)) { - VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); - PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); - } + if (!isNumber(arg)) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } - } else { + final int number; + try { + number = Integer.parseInt(arg); + if (number == 0) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } + } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } + + if (EconomyOperations.refundOnDelete(player, number)) { + VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); + PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); } } @@ -327,27 +377,37 @@ public static void deleteOtherVault(CommandSender sender, String holder, String if (isLocked()) { return; } - if (sender.hasPermission(Permission.DELETE)) { - if (isNumber(arg)) { - int number = 0; - try { - number = Integer.parseInt(arg); - if (number == 0) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - return; - } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - } + if (!sender.hasPermission(Permission.DELETE)) { + PlayerVaults.getInstance().getTL().noPerms().title().send(sender); + return; + } + if (!isNumber(arg)) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; + } - VaultManager.getInstance().deleteVault(sender, holder, number); - PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", holder).send(sender); - } else { + final int number; + try { + number = Integer.parseInt(arg); + if (number == 0) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; } - } else { - PlayerVaults.getInstance().getTL().noPerms().title().send(sender); + } catch (NumberFormatException nfe) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; + } + + VaultManager.getInstance().deleteVault(sender, holder, number); + String display = holder; + try { + UUID uuid = UUID.fromString(holder); + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + if (op.getName() != null) display = op.getName(); + } catch (Exception e) { + // The return instance here can be suppressed } + PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", display).send(sender); } /** @@ -387,9 +447,9 @@ private record PlayerCount(int count, Instant time) { public static int countVaults(Player player) { UUID uuid = player.getUniqueId(); - PlayerCount count = countCache.get(uuid); - if (count != null && count.time().isAfter(Instant.now().plus(secondsToLive, ChronoUnit.SECONDS))) { - return count.count; + PlayerCount cached = countCache.get(uuid); + if (cached != null && Instant.now().isBefore(cached.time().plus(secondsToLive, ChronoUnit.SECONDS))) { + return cached.count; } int vaultCount = 0; for (int x = 1; x <= PlayerVaults.getInstance().getMaxVaultAmountPermTest(); x++) { diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java index 756b9d2..0dbf646 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java @@ -29,6 +29,7 @@ public class VaultViewInfo { /** * Makes a VaultViewInfo object. Used for opening a vault owned by the opener. * + * @param vaultName UUID (string) or legacy key * @param i vault number. */ public VaultViewInfo(String vaultName, int i) { @@ -58,4 +59,4 @@ public int getNumber() { public String toString() { return this.vaultName + " " + this.number; } -} \ No newline at end of file +} From 5864d3fd4cf86d43475df99c621626cd28a1890a Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 16:51:54 -0400 Subject: [PATCH 03/11] Adjust logic --- src/main/java/com/drtshock/playervaults/PlayerVaults.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index 26c677a..af7f096 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -38,6 +38,7 @@ import com.drtshock.playervaults.vaultmanagement.EconomyOperations; import com.drtshock.playervaults.vaultmanagement.VaultManager; import com.drtshock.playervaults.vaultmanagement.VaultViewInfo; +import com.google.common.collect.Sets; import com.google.gson.Gson; import com.tcoded.folialib.FoliaLib; import com.tcoded.folialib.impl.PlatformScheduler; @@ -104,8 +105,8 @@ public class PlayerVaults extends JavaPlugin { private final ConcurrentHashMap inVault = new ConcurrentHashMap<>(); // VaultViewInfo - Inventory private final ConcurrentHashMap openInventories = new ConcurrentHashMap<>(); - private final Set blockedMats = ConcurrentHashMap.newKeySet(); - private final Set blockedEnchs = ConcurrentHashMap.newKeySet(); + private final Set blockedMats = Sets.newConcurrentHashSet(); + private final Set blockedEnchs = Sets.newConcurrentHashSet(); private boolean blockWithModelData = false; private boolean blockWithoutModelData = false; private boolean useVault; @@ -739,7 +740,7 @@ public Component getComponent() { } } - private final Set told = ConcurrentHashMap.newKeySet(); + private final Set told = Sets.newConcurrentHashSet(); public void updateNotification(Player player) { if (updateResponse == null || !player.hasPermission(Permission.ADMIN)) { From 3f9f9dd41a1087bf60476b3c68ca04796df7839e Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 17:45:35 -0400 Subject: [PATCH 04/11] Revert "Comprehensive safety checks to match explicit requirements (experimental)" This reverts commit a260be61d407458befde40db685bf12803e11cee. --- .../drtshock/playervaults/PlayerVaults.java | 4 +- .../vaultmanagement/VaultManager.java | 221 ++++---------- .../vaultmanagement/VaultOperations.java | 270 +++++++----------- .../vaultmanagement/VaultViewInfo.java | 3 +- 4 files changed, 167 insertions(+), 331 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index af7f096..c9e45c2 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -42,7 +42,6 @@ import com.google.gson.Gson; import com.tcoded.folialib.FoliaLib; import com.tcoded.folialib.impl.PlatformScheduler; -import dev.kitteh.cardboardbox.CardboardBox; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -62,6 +61,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import dev.kitteh.cardboardbox.CardboardBox; import sun.misc.Unsafe; import java.io.BufferedReader; @@ -374,7 +374,7 @@ public void onDisable() { Inventory inventory = player.getOpenInventory().getTopInventory(); if (inventory.getViewers().size() == 1) { VaultViewInfo info = this.inVault.get(player.getUniqueId().toString()); - VaultManager.getInstance().saveVault(inventory, info.getVaultName(), info.getNumber()); + VaultManager.getInstance().saveVault(inventory, player.getUniqueId().toString(), info.getNumber()); this.openInventories.remove(info.toString()); // try this to make sure that they can't make further edits if the process hangs. PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 8e99dd2..6cf58e8 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -30,18 +30,13 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -import static com.drtshock.playervaults.vaultmanagement.VaultOperations.VaultGate; - public class VaultManager { private static final String VAULTKEY = "vault%d"; @@ -64,33 +59,6 @@ public static VaultManager getInstance() { return instance; } - /** - * Resolve a stable, UUID-first key for a holder. - */ - public static String normalizeHolderKey(String input) { - if (input == null) { - return null; - } - - File dataDir = PlayerVaults.getInstance().getVaultData(); - File legacy = new File(dataDir, input + ".yml"); - if (legacy.exists()) { - return input; - } - - try { - UUID uuid = UUID.fromString(input); - return uuid.toString(); - } catch (Exception ignored) {} - - OfflinePlayer byName = Bukkit.getOfflinePlayer(input); - if (byName != null && byName.getUniqueId() != null) { - return byName.getUniqueId().toString(); - } - - return input; - } - /** * Saves the inventory to the specified player and vault number. * @@ -99,14 +67,11 @@ public static String normalizeHolderKey(String input) { * @param number The vault number. */ public void saveVault(Inventory inventory, String target, int number) { - final String holderKey = normalizeHolderKey(target); - VaultGate.withLock(new VaultGate.VaultKey(holderKey, number), () -> { - YamlConfiguration yaml = getPlayerVaultFile(holderKey, true); - VaultOperations.getMaxVaultSize(holderKey); - String serialized = CardboardBoxSerialization.toStorage(inventory, holderKey); - yaml.set(String.format(VAULTKEY, number), serialized); - saveFileSync(holderKey, yaml); - }); + YamlConfiguration yaml = getPlayerVaultFile(target, true); + VaultOperations.getMaxVaultSize(target); + String serialized = CardboardBoxSerialization.toStorage(inventory, target); + yaml.set(String.format(VAULTKEY, number), serialized); + saveFileSync(target, yaml); } /** @@ -152,19 +117,28 @@ public Inventory loadOtherVault(String name, int number, int size) { size = PlayerVaults.getInstance().getDefaultVaultSize(); } - final String holderKey = normalizeHolderKey(name); - PlayerVaults.debug("Loading other vault for " + holderKey); + PlayerVaults.debug("Loading other vault for " + name); + + String holder = name; + + try { + UUID uuid = UUID.fromString(name); + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + holder = offlinePlayer.getUniqueId().toString(); + } catch (Exception e) { + // Not a player + } String title = PlayerVaults.getInstance().getVaultTitle(String.valueOf(number)); - VaultViewInfo info = new VaultViewInfo(holderKey, number); + VaultViewInfo info = new VaultViewInfo(name, number); Inventory inv; VaultHolder vaultHolder = new VaultHolder(number); if (PlayerVaults.getInstance().getOpenInventories().containsKey(info.toString())) { PlayerVaults.debug("Already open"); inv = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); } else { - YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); - Inventory i = getInventory(vaultHolder, holderKey, playerFile, size, number, title); + YamlConfiguration playerFile = getPlayerVaultFile(holder, true); + Inventory i = getInventory(vaultHolder, holder, playerFile, size, number, title); if (i == null) { return null; } else { @@ -197,7 +171,7 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf // Happens on change of permission or if people used the broken version. // In this case, players will lose items. if (deserialized.length > size) { - PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size + ". Attempting to rescue!"); + PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size+". Attempting to rescue!"); for (ItemStack stack : deserialized) { if (stack != null) { inventory.addItem(stack); @@ -219,14 +193,11 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf * @return The inventory of the specified holder and vault number. Can be null. */ public Inventory getVault(String holder, int number) { - String holderKey = normalizeHolderKey(holder); - YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); + YamlConfiguration playerFile = getPlayerVaultFile(holder, true); String serialized = playerFile.getString(String.format(VAULTKEY, number)); - ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holderKey); - int size = Math.max(9, ((contents.length + 8) / 9) * 9); - Inventory inventory = Bukkit.createInventory(null, size, holderKey + " vault " + number); - ItemStack[] copy = Arrays.copyOf(contents, size); - inventory.setContents(copy); + ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holder); + Inventory inventory = Bukkit.createInventory(null, contents.length, holder + " vault " + number); + inventory.setContents(contents); return inventory; } @@ -238,13 +209,12 @@ public Inventory getVault(String holder, int number) { * @return true if the vault file and vault number exist in that file, otherwise false. */ public boolean vaultExists(String holder, int number) { - String holderKey = normalizeHolderKey(holder); - File file = new File(directory, holderKey + ".yml"); + File file = new File(directory, holder + ".yml"); if (!file.exists()) { return false; } - return getPlayerVaultFile(holderKey, true).contains(String.format(VAULTKEY, number)); + return getPlayerVaultFile(holder, true).contains(String.format(VAULTKEY, number)); } /** @@ -255,8 +225,7 @@ public boolean vaultExists(String holder, int number) { */ public Set getVaultNumbers(String holder) { Set vaults = new HashSet<>(); - String holderKey = normalizeHolderKey(holder); - YamlConfiguration file = getPlayerVaultFile(holderKey, true); + YamlConfiguration file = getPlayerVaultFile(holder, true); if (file == null) { return vaults; } @@ -275,28 +244,8 @@ public Set getVaultNumbers(String holder) { } public void deleteAllVaults(String holder) { - String holderKey = normalizeHolderKey(holder); - removeCachedPlayerVaultFile(holderKey); - deletePlayerVaultFile(holderKey); - - List toRemove = new ArrayList<>(); - PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { - if (holderKey.equals(info.getVaultName())) { - toRemove.add(viewerId); - Player p = null; - try { - p = Bukkit.getPlayer(UUID.fromString(viewerId)); - } catch (Exception ignored) { - // Ignored try actionable. - } - if (p != null) { - Player finalP = p; - PlayerVaults.scheduler().runAtEntity(finalP, task -> finalP.closeInventory()); - } - PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); - } - }); - toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); + removeCachedPlayerVaultFile(holder); + deletePlayerVaultFile(holder); } /** @@ -307,82 +256,48 @@ public void deleteAllVaults(String holder) { * @param number The vault number. */ public void deleteVault(CommandSender sender, final String holder, final int number) { - final String holderKey = normalizeHolderKey(holder); - final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); - - PlayerVaults.scheduler().runAsync(task -> - VaultGate.withLock(gateKey, () -> { - File file = new File(directory, holderKey + ".yml"); - if (!file.exists()) { - return; - } + PlayerVaults.scheduler().runAsync(task -> { + File file = new File(directory, holder + ".yml"); + if (!file.exists()) { + return; + } - YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); + YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); + if (file.exists()) { playerFile.set(String.format(VAULTKEY, number), null); - cachedVaultFiles.put(holderKey, playerFile); + if (cachedVaultFiles.containsKey(holder)) { + cachedVaultFiles.put(holder, playerFile); + } try { playerFile.save(file); } catch (IOException ignored) { } - - String key = new VaultViewInfo(holderKey, number).toString(); - PlayerVaults.getInstance().getOpenInventories().remove(key); - - List toRemove = new ArrayList<>(); - PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { - if (holderKey.equals(info.getVaultName()) && info.getNumber() == number) { - toRemove.add(viewerId); - Player p = null; - try { - p = Bukkit.getPlayer(UUID.fromString(viewerId)); - } catch (Exception ignored) { - // Ignored try actionable. - } - if (p != null) { - Player finalP = p; - PlayerVaults.scheduler().runAtEntity(finalP, t -> finalP.closeInventory()); - } - } - }); - toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); - }) - ); - - OfflinePlayer target = null; - try { - target = Bukkit.getOfflinePlayer(UUID.fromString(holderKey)); - } catch (Exception ignored) { - // Ignored try actionable. - } - if (target != null && target.getName() != null) { - if (sender.getName().equalsIgnoreCase(target.getName())) { - this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); - } else { - this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", target.getName()).send(sender); } - } else { - if (sender.getName().equalsIgnoreCase(holder)) { + }); + + OfflinePlayer player = Bukkit.getPlayer(holder); + if (player != null) { + if (sender.getName().equalsIgnoreCase(player.getName())) { this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); } else { - this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", holder).send(sender); + this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", player.getName()).send(sender); } } - PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(holderKey, number).toString()); + String vaultName = sender instanceof Player ? ((Player) sender).getUniqueId().toString() : holder; + PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(vaultName, number).toString()); } // Should only be run asynchronously public void cachePlayerVaultFile(String holder) { - String holderKey = normalizeHolderKey(holder); - YamlConfiguration config = this.loadPlayerVaultFile(holderKey, false); + YamlConfiguration config = this.loadPlayerVaultFile(holder, false); if (config != null) { - this.cachedVaultFiles.put(holderKey, config); + this.cachedVaultFiles.put(holder, config); } } public void removeCachedPlayerVaultFile(String holder) { - String holderKey = normalizeHolderKey(holder); - cachedVaultFiles.remove(holderKey); + cachedVaultFiles.remove(holder); } /** @@ -392,8 +307,7 @@ public void removeCachedPlayerVaultFile(String holder) { * @return The holder's vault config file. */ public YamlConfiguration getPlayerVaultFile(String holder, boolean createIfNotFound) { - String holderKey = resolveFileKey(holder); - return cachedVaultFiles.computeIfAbsent(holderKey, key -> loadPlayerVaultFile(key, createIfNotFound)); + return cachedVaultFiles.computeIfAbsent(holder, key -> loadPlayerVaultFile(key, createIfNotFound)); } public YamlConfiguration loadPlayerVaultFile(String holder) { @@ -406,8 +320,7 @@ public YamlConfiguration loadPlayerVaultFile(String holder) { * @param holder UUID of the holder. */ public void deletePlayerVaultFile(String holder) { - String holderKey = resolveFileKey(holder); - File file = new File(this.directory, holderKey + ".yml"); + File file = new File(this.directory, holder + ".yml"); if (file.exists()) { file.delete(); } @@ -435,38 +348,22 @@ public YamlConfiguration loadPlayerVaultFile(String uniqueId, boolean createIfNo } public void saveFileSync(final String holder, final YamlConfiguration yaml) { - String holderKey = resolveFileKey(holder); - if (cachedVaultFiles.containsKey(holderKey)) { - cachedVaultFiles.put(holderKey, yaml); + if (cachedVaultFiles.containsKey(holder)) { + cachedVaultFiles.put(holder, yaml); } final boolean backups = PlayerVaults.getInstance().isBackupsEnabled(); final File backupsFolder = PlayerVaults.getInstance().getBackupsFolder(); - final File file = new File(directory, holderKey + ".yml"); + final File file = new File(directory, holder + ".yml"); if (file.exists() && backups) { - file.renameTo(new File(backupsFolder, holderKey + ".yml")); + file.renameTo(new File(backupsFolder, holder + ".yml")); } - try { yaml.save(file); } catch (IOException e) { - PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holderKey, e)); - PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holderKey, e); - } - - PlayerVaults.debug("Saved vault for " + holderKey); - } - - private String resolveFileKey(String holder) { - if (holder == null) { - return null; + PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holder, e)); + PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holder, e); } - - File legacy = new File(this.directory, holder + ".yml"); - if (legacy.exists()) { - return holder; - } - - return normalizeHolderKey(holder); + PlayerVaults.debug("Saved vault for " + holder); } } diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index cebe69f..c37bcfa 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -34,48 +34,11 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; public class VaultOperations { private static final AtomicBoolean LOCKED = new AtomicBoolean(false); - public static final class VaultGate { - - private static final ConcurrentHashMap LOCKS = new ConcurrentHashMap<>(); - - public static T withLock(VaultKey key, Supplier body) { - ReentrantLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantLock()); - lock.lock(); - try { - return body.get(); - } finally { - try { - lock.unlock(); - } finally { - if (!lock.hasQueuedThreads()) { - LOCKS.remove(key, lock); - } - } - } - } - - public static void withLock(VaultKey key, Runnable body) { - withLock(key, () -> { - body.run(); - return null; - }); - } - - public record VaultKey(String ownerKey, int number) { - @Override - public String toString() { - return ownerKey + " " + number; - } - } - } - /** * Gets whether or not player vaults are locked * @@ -96,10 +59,12 @@ public static void setLocked(boolean locked) { if (locked) { for (Player player : PlayerVaults.getInstance().getServer().getOnlinePlayers()) { - InventoryView view = player.getOpenInventory(); - if (view != null && view.getTopInventory() != null && view.getTopInventory().getHolder() instanceof VaultHolder) { - PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); - PlayerVaults.getInstance().getTL().locked().title().send(player); + if (player.getOpenInventory() != null) { + InventoryView view = player.getOpenInventory(); + if (view.getTopInventory().getHolder() instanceof VaultHolder) { + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); + PlayerVaults.getInstance().getTL().locked().title().send(player); + } } } } @@ -178,7 +143,7 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo if (player.isSleeping() || player.isDead() || !player.isOnline()) { return false; } - final int number; + int number; try { number = Integer.parseInt(arg); if (number < 1) { @@ -189,44 +154,49 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo return false; } - if (!checkPerms(player, number)) { - PlayerVaults.getInstance().getTL().noPerms().title().send(player); - return false; - } + if (checkPerms(player, number)) { + if (free || EconomyOperations.payToOpen(player, number)) { + VaultViewInfo info = new VaultViewInfo(player.getUniqueId().toString(), number); - if (!free && !EconomyOperations.payToOpen(player, number)) { - PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); - return false; - } - - final String ownerKey = player.getUniqueId().toString(); - final VaultViewInfo info = new VaultViewInfo(ownerKey, number); - final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(ownerKey, number); + final Inventory inv = PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), (key) -> { + Inventory loadedVault = VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)); + if (loadedVault == null) { + PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); + } + return loadedVault; + }); - Inventory inv = VaultGate.withLock(gateKey, () -> - PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), key -> - VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)) - ) - ); + if (inv == null) { + PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); + return false; + } - if (inv == null) { - PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); - return false; - } + if (PlayerVaults.getInstance().getOpenInventories().get(info.toString()) != inv) { + PlayerVaults.debug("Vault for " + player.getName() + " was already opened by another thread."); + return true; + } - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); - return false; // inventory open event was cancelled. - } + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); + return false; // inventory open event was cancelled. + } - if (send) { - PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); + if (send) { + PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); + } + return true; + } else { + PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); + return false; + } + } else { + PlayerVaults.getInstance().getTL().noPerms().title().send(player); } - return true; + return false; } /** @@ -249,7 +219,7 @@ public static boolean openOwnVault(Player player, String arg, boolean isCommand) * Open another player's vault. * * @param player The player to open to. - * @param vaultOwner The name or UUID of the vault owner. + * @param vaultOwner The name of the vault owner. * @param arg The vault number to open. * @return Whether or not the player was allowed to open it. */ @@ -266,7 +236,9 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar return false; } - final int number; + long time = System.currentTimeMillis(); + + int number = 0; try { number = Integer.parseInt(arg); if (number < 1) { @@ -275,61 +247,41 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar } } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return false; } - final String holderKey = VaultManager.normalizeHolderKey(vaultOwner); - final VaultViewInfo info = new VaultViewInfo(holderKey, number); - final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); - - long time = System.currentTimeMillis(); - - Inventory inv = VaultGate.withLock(gateKey, () -> { - Inventory cached = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); - if (cached != null) { - return cached; - } - Inventory loaded = VaultManager.getInstance().loadOtherVault(holderKey, number, getMaxVaultSize(holderKey)); - if (loaded != null) { - PlayerVaults.getInstance().getOpenInventories().put(info.toString(), loaded); - } - return loaded; - }); - - // Resolve a nice display name if possible - String displayName = holderKey; + Inventory inv = VaultManager.getInstance().loadOtherVault(vaultOwner, number, getMaxVaultSize(vaultOwner)); + String name = vaultOwner; try { - UUID uuid = UUID.fromString(holderKey); - OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); - if (op.getName() != null) { - displayName = op.getName(); - } + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(UUID.fromString(vaultOwner)); + name = offlinePlayer.getName(); } catch (Exception e) { // not a player } if (inv == null) { PlayerVaults.getInstance().getTL().vaultDoesNotExist().title().send(player); - PlayerVaults.debug("Opening other vault that does not fully exist.", time); - return false; - } - - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + } else { + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - return false; // inventory open event was cancelled. - } + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + return false; // inventory open event was cancelled. + } + if (send) { + PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", name).send(player); + } + PlayerVaults.debug("opening other vault", time); - if (send) { - PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", displayName).send(player); + // Need to set ViewInfo for a third party vault for the opening player. + VaultViewInfo info = new VaultViewInfo(vaultOwner, number); + PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); + PlayerVaults.getInstance().getOpenInventories().put(player.getUniqueId().toString(), inv); + return true; } - PlayerVaults.debug("opening other vault", time); - // Track which vault this viewer is in - PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); - return true; + PlayerVaults.debug("opening other vault returning false", time); + return false; } /** @@ -342,27 +294,25 @@ public static void deleteOwnVault(Player player, String arg) { if (isLocked()) { return; } - - if (!isNumber(arg)) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return; - } - - final int number; - try { - number = Integer.parseInt(arg); - if (number == 0) { + if (isNumber(arg)) { + int number = 0; + try { + number = Integer.parseInt(arg); + if (number == 0) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } + } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return; } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return; - } - if (EconomyOperations.refundOnDelete(player, number)) { - VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); - PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); + if (EconomyOperations.refundOnDelete(player, number)) { + VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); + PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); + } + + } else { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); } } @@ -377,37 +327,27 @@ public static void deleteOtherVault(CommandSender sender, String holder, String if (isLocked()) { return; } - if (!sender.hasPermission(Permission.DELETE)) { - PlayerVaults.getInstance().getTL().noPerms().title().send(sender); - return; - } - if (!isNumber(arg)) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - return; - } + if (sender.hasPermission(Permission.DELETE)) { + if (isNumber(arg)) { + int number = 0; + try { + number = Integer.parseInt(arg); + if (number == 0) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; + } + } catch (NumberFormatException nfe) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + } - final int number; - try { - number = Integer.parseInt(arg); - if (number == 0) { + VaultManager.getInstance().deleteVault(sender, holder, number); + PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", holder).send(sender); + } else { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - return; } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - return; - } - - VaultManager.getInstance().deleteVault(sender, holder, number); - String display = holder; - try { - UUID uuid = UUID.fromString(holder); - OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); - if (op.getName() != null) display = op.getName(); - } catch (Exception e) { - // The return instance here can be suppressed + } else { + PlayerVaults.getInstance().getTL().noPerms().title().send(sender); } - PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", display).send(sender); } /** @@ -447,9 +387,9 @@ private record PlayerCount(int count, Instant time) { public static int countVaults(Player player) { UUID uuid = player.getUniqueId(); - PlayerCount cached = countCache.get(uuid); - if (cached != null && Instant.now().isBefore(cached.time().plus(secondsToLive, ChronoUnit.SECONDS))) { - return cached.count; + PlayerCount count = countCache.get(uuid); + if (count != null && count.time().isAfter(Instant.now().plus(secondsToLive, ChronoUnit.SECONDS))) { + return count.count; } int vaultCount = 0; for (int x = 1; x <= PlayerVaults.getInstance().getMaxVaultAmountPermTest(); x++) { diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java index 0dbf646..756b9d2 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java @@ -29,7 +29,6 @@ public class VaultViewInfo { /** * Makes a VaultViewInfo object. Used for opening a vault owned by the opener. * - * @param vaultName UUID (string) or legacy key * @param i vault number. */ public VaultViewInfo(String vaultName, int i) { @@ -59,4 +58,4 @@ public int getNumber() { public String toString() { return this.vaultName + " " + this.number; } -} +} \ No newline at end of file From eeb1d18612ab2d74e6152fb27b77198defe45c62 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 17:48:42 -0400 Subject: [PATCH 05/11] Reapply "Comprehensive safety checks to match explicit requirements (experimental)" This reverts commit 3f9f9dd41a1087bf60476b3c68ca04796df7839e. --- .../drtshock/playervaults/PlayerVaults.java | 4 +- .../vaultmanagement/VaultManager.java | 221 ++++++++++---- .../vaultmanagement/VaultOperations.java | 270 +++++++++++------- .../vaultmanagement/VaultViewInfo.java | 3 +- 4 files changed, 331 insertions(+), 167 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index c9e45c2..af7f096 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -42,6 +42,7 @@ import com.google.gson.Gson; import com.tcoded.folialib.FoliaLib; import com.tcoded.folialib.impl.PlatformScheduler; +import dev.kitteh.cardboardbox.CardboardBox; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -61,7 +62,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; -import dev.kitteh.cardboardbox.CardboardBox; import sun.misc.Unsafe; import java.io.BufferedReader; @@ -374,7 +374,7 @@ public void onDisable() { Inventory inventory = player.getOpenInventory().getTopInventory(); if (inventory.getViewers().size() == 1) { VaultViewInfo info = this.inVault.get(player.getUniqueId().toString()); - VaultManager.getInstance().saveVault(inventory, player.getUniqueId().toString(), info.getNumber()); + VaultManager.getInstance().saveVault(inventory, info.getVaultName(), info.getNumber()); this.openInventories.remove(info.toString()); // try this to make sure that they can't make further edits if the process hangs. PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 6cf58e8..8e99dd2 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -30,13 +30,18 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import static com.drtshock.playervaults.vaultmanagement.VaultOperations.VaultGate; + public class VaultManager { private static final String VAULTKEY = "vault%d"; @@ -59,6 +64,33 @@ public static VaultManager getInstance() { return instance; } + /** + * Resolve a stable, UUID-first key for a holder. + */ + public static String normalizeHolderKey(String input) { + if (input == null) { + return null; + } + + File dataDir = PlayerVaults.getInstance().getVaultData(); + File legacy = new File(dataDir, input + ".yml"); + if (legacy.exists()) { + return input; + } + + try { + UUID uuid = UUID.fromString(input); + return uuid.toString(); + } catch (Exception ignored) {} + + OfflinePlayer byName = Bukkit.getOfflinePlayer(input); + if (byName != null && byName.getUniqueId() != null) { + return byName.getUniqueId().toString(); + } + + return input; + } + /** * Saves the inventory to the specified player and vault number. * @@ -67,11 +99,14 @@ public static VaultManager getInstance() { * @param number The vault number. */ public void saveVault(Inventory inventory, String target, int number) { - YamlConfiguration yaml = getPlayerVaultFile(target, true); - VaultOperations.getMaxVaultSize(target); - String serialized = CardboardBoxSerialization.toStorage(inventory, target); - yaml.set(String.format(VAULTKEY, number), serialized); - saveFileSync(target, yaml); + final String holderKey = normalizeHolderKey(target); + VaultGate.withLock(new VaultGate.VaultKey(holderKey, number), () -> { + YamlConfiguration yaml = getPlayerVaultFile(holderKey, true); + VaultOperations.getMaxVaultSize(holderKey); + String serialized = CardboardBoxSerialization.toStorage(inventory, holderKey); + yaml.set(String.format(VAULTKEY, number), serialized); + saveFileSync(holderKey, yaml); + }); } /** @@ -117,28 +152,19 @@ public Inventory loadOtherVault(String name, int number, int size) { size = PlayerVaults.getInstance().getDefaultVaultSize(); } - PlayerVaults.debug("Loading other vault for " + name); - - String holder = name; - - try { - UUID uuid = UUID.fromString(name); - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); - holder = offlinePlayer.getUniqueId().toString(); - } catch (Exception e) { - // Not a player - } + final String holderKey = normalizeHolderKey(name); + PlayerVaults.debug("Loading other vault for " + holderKey); String title = PlayerVaults.getInstance().getVaultTitle(String.valueOf(number)); - VaultViewInfo info = new VaultViewInfo(name, number); + VaultViewInfo info = new VaultViewInfo(holderKey, number); Inventory inv; VaultHolder vaultHolder = new VaultHolder(number); if (PlayerVaults.getInstance().getOpenInventories().containsKey(info.toString())) { PlayerVaults.debug("Already open"); inv = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); } else { - YamlConfiguration playerFile = getPlayerVaultFile(holder, true); - Inventory i = getInventory(vaultHolder, holder, playerFile, size, number, title); + YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); + Inventory i = getInventory(vaultHolder, holderKey, playerFile, size, number, title); if (i == null) { return null; } else { @@ -171,7 +197,7 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf // Happens on change of permission or if people used the broken version. // In this case, players will lose items. if (deserialized.length > size) { - PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size+". Attempting to rescue!"); + PlayerVaults.debug("Loaded vault for " + ownerName + " and got " + deserialized.length + " items for allowed size of " + size + ". Attempting to rescue!"); for (ItemStack stack : deserialized) { if (stack != null) { inventory.addItem(stack); @@ -193,11 +219,14 @@ private Inventory getInventory(InventoryHolder owner, String ownerName, YamlConf * @return The inventory of the specified holder and vault number. Can be null. */ public Inventory getVault(String holder, int number) { - YamlConfiguration playerFile = getPlayerVaultFile(holder, true); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration playerFile = getPlayerVaultFile(holderKey, true); String serialized = playerFile.getString(String.format(VAULTKEY, number)); - ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holder); - Inventory inventory = Bukkit.createInventory(null, contents.length, holder + " vault " + number); - inventory.setContents(contents); + ItemStack[] contents = CardboardBoxSerialization.fromStorage(serialized, holderKey); + int size = Math.max(9, ((contents.length + 8) / 9) * 9); + Inventory inventory = Bukkit.createInventory(null, size, holderKey + " vault " + number); + ItemStack[] copy = Arrays.copyOf(contents, size); + inventory.setContents(copy); return inventory; } @@ -209,12 +238,13 @@ public Inventory getVault(String holder, int number) { * @return true if the vault file and vault number exist in that file, otherwise false. */ public boolean vaultExists(String holder, int number) { - File file = new File(directory, holder + ".yml"); + String holderKey = normalizeHolderKey(holder); + File file = new File(directory, holderKey + ".yml"); if (!file.exists()) { return false; } - return getPlayerVaultFile(holder, true).contains(String.format(VAULTKEY, number)); + return getPlayerVaultFile(holderKey, true).contains(String.format(VAULTKEY, number)); } /** @@ -225,7 +255,8 @@ public boolean vaultExists(String holder, int number) { */ public Set getVaultNumbers(String holder) { Set vaults = new HashSet<>(); - YamlConfiguration file = getPlayerVaultFile(holder, true); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration file = getPlayerVaultFile(holderKey, true); if (file == null) { return vaults; } @@ -244,8 +275,28 @@ public Set getVaultNumbers(String holder) { } public void deleteAllVaults(String holder) { - removeCachedPlayerVaultFile(holder); - deletePlayerVaultFile(holder); + String holderKey = normalizeHolderKey(holder); + removeCachedPlayerVaultFile(holderKey); + deletePlayerVaultFile(holderKey); + + List toRemove = new ArrayList<>(); + PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { + if (holderKey.equals(info.getVaultName())) { + toRemove.add(viewerId); + Player p = null; + try { + p = Bukkit.getPlayer(UUID.fromString(viewerId)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (p != null) { + Player finalP = p; + PlayerVaults.scheduler().runAtEntity(finalP, task -> finalP.closeInventory()); + } + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); + } + }); + toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); } /** @@ -256,48 +307,82 @@ public void deleteAllVaults(String holder) { * @param number The vault number. */ public void deleteVault(CommandSender sender, final String holder, final int number) { - PlayerVaults.scheduler().runAsync(task -> { - File file = new File(directory, holder + ".yml"); - if (!file.exists()) { - return; - } + final String holderKey = normalizeHolderKey(holder); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); + + PlayerVaults.scheduler().runAsync(task -> + VaultGate.withLock(gateKey, () -> { + File file = new File(directory, holderKey + ".yml"); + if (!file.exists()) { + return; + } - YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); - if (file.exists()) { + YamlConfiguration playerFile = YamlConfiguration.loadConfiguration(file); playerFile.set(String.format(VAULTKEY, number), null); - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, playerFile); - } + cachedVaultFiles.put(holderKey, playerFile); try { playerFile.save(file); } catch (IOException ignored) { } - } - }); - OfflinePlayer player = Bukkit.getPlayer(holder); - if (player != null) { - if (sender.getName().equalsIgnoreCase(player.getName())) { + String key = new VaultViewInfo(holderKey, number).toString(); + PlayerVaults.getInstance().getOpenInventories().remove(key); + + List toRemove = new ArrayList<>(); + PlayerVaults.getInstance().getInVault().forEach((viewerId, info) -> { + if (holderKey.equals(info.getVaultName()) && info.getNumber() == number) { + toRemove.add(viewerId); + Player p = null; + try { + p = Bukkit.getPlayer(UUID.fromString(viewerId)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (p != null) { + Player finalP = p; + PlayerVaults.scheduler().runAtEntity(finalP, t -> finalP.closeInventory()); + } + } + }); + toRemove.forEach(id -> PlayerVaults.getInstance().getInVault().remove(id)); + }) + ); + + OfflinePlayer target = null; + try { + target = Bukkit.getOfflinePlayer(UUID.fromString(holderKey)); + } catch (Exception ignored) { + // Ignored try actionable. + } + if (target != null && target.getName() != null) { + if (sender.getName().equalsIgnoreCase(target.getName())) { this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); } else { - this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", player.getName()).send(sender); + this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", target.getName()).send(sender); + } + } else { + if (sender.getName().equalsIgnoreCase(holder)) { + this.plugin.getTL().deleteVault().title().with("vault", String.valueOf(number)).send(sender); + } else { + this.plugin.getTL().deleteOtherVault().title().with("vault", String.valueOf(number)).with("player", holder).send(sender); } } - String vaultName = sender instanceof Player ? ((Player) sender).getUniqueId().toString() : holder; - PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(vaultName, number).toString()); + PlayerVaults.getInstance().getOpenInventories().remove(new VaultViewInfo(holderKey, number).toString()); } // Should only be run asynchronously public void cachePlayerVaultFile(String holder) { - YamlConfiguration config = this.loadPlayerVaultFile(holder, false); + String holderKey = normalizeHolderKey(holder); + YamlConfiguration config = this.loadPlayerVaultFile(holderKey, false); if (config != null) { - this.cachedVaultFiles.put(holder, config); + this.cachedVaultFiles.put(holderKey, config); } } public void removeCachedPlayerVaultFile(String holder) { - cachedVaultFiles.remove(holder); + String holderKey = normalizeHolderKey(holder); + cachedVaultFiles.remove(holderKey); } /** @@ -307,7 +392,8 @@ public void removeCachedPlayerVaultFile(String holder) { * @return The holder's vault config file. */ public YamlConfiguration getPlayerVaultFile(String holder, boolean createIfNotFound) { - return cachedVaultFiles.computeIfAbsent(holder, key -> loadPlayerVaultFile(key, createIfNotFound)); + String holderKey = resolveFileKey(holder); + return cachedVaultFiles.computeIfAbsent(holderKey, key -> loadPlayerVaultFile(key, createIfNotFound)); } public YamlConfiguration loadPlayerVaultFile(String holder) { @@ -320,7 +406,8 @@ public YamlConfiguration loadPlayerVaultFile(String holder) { * @param holder UUID of the holder. */ public void deletePlayerVaultFile(String holder) { - File file = new File(this.directory, holder + ".yml"); + String holderKey = resolveFileKey(holder); + File file = new File(this.directory, holderKey + ".yml"); if (file.exists()) { file.delete(); } @@ -348,22 +435,38 @@ public YamlConfiguration loadPlayerVaultFile(String uniqueId, boolean createIfNo } public void saveFileSync(final String holder, final YamlConfiguration yaml) { - if (cachedVaultFiles.containsKey(holder)) { - cachedVaultFiles.put(holder, yaml); + String holderKey = resolveFileKey(holder); + if (cachedVaultFiles.containsKey(holderKey)) { + cachedVaultFiles.put(holderKey, yaml); } final boolean backups = PlayerVaults.getInstance().isBackupsEnabled(); final File backupsFolder = PlayerVaults.getInstance().getBackupsFolder(); - final File file = new File(directory, holder + ".yml"); + final File file = new File(directory, holderKey + ".yml"); if (file.exists() && backups) { - file.renameTo(new File(backupsFolder, holder + ".yml")); + file.renameTo(new File(backupsFolder, holderKey + ".yml")); } + try { yaml.save(file); } catch (IOException e) { - PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holder, e)); - PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holder, e); + PlayerVaults.getInstance().addException(new IllegalStateException("Failed to save vault file for: " + holderKey, e)); + PlayerVaults.getInstance().getLogger().log(Level.SEVERE, "Failed to save vault file for: " + holderKey, e); + } + + PlayerVaults.debug("Saved vault for " + holderKey); + } + + private String resolveFileKey(String holder) { + if (holder == null) { + return null; } - PlayerVaults.debug("Saved vault for " + holder); + + File legacy = new File(this.directory, holder + ".yml"); + if (legacy.exists()) { + return holder; + } + + return normalizeHolderKey(holder); } } diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index c37bcfa..cebe69f 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -34,11 +34,48 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; public class VaultOperations { private static final AtomicBoolean LOCKED = new AtomicBoolean(false); + public static final class VaultGate { + + private static final ConcurrentHashMap LOCKS = new ConcurrentHashMap<>(); + + public static T withLock(VaultKey key, Supplier body) { + ReentrantLock lock = LOCKS.computeIfAbsent(key, k -> new ReentrantLock()); + lock.lock(); + try { + return body.get(); + } finally { + try { + lock.unlock(); + } finally { + if (!lock.hasQueuedThreads()) { + LOCKS.remove(key, lock); + } + } + } + } + + public static void withLock(VaultKey key, Runnable body) { + withLock(key, () -> { + body.run(); + return null; + }); + } + + public record VaultKey(String ownerKey, int number) { + @Override + public String toString() { + return ownerKey + " " + number; + } + } + } + /** * Gets whether or not player vaults are locked * @@ -59,12 +96,10 @@ public static void setLocked(boolean locked) { if (locked) { for (Player player : PlayerVaults.getInstance().getServer().getOnlinePlayers()) { - if (player.getOpenInventory() != null) { - InventoryView view = player.getOpenInventory(); - if (view.getTopInventory().getHolder() instanceof VaultHolder) { - PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); - PlayerVaults.getInstance().getTL().locked().title().send(player); - } + InventoryView view = player.getOpenInventory(); + if (view != null && view.getTopInventory() != null && view.getTopInventory().getHolder() instanceof VaultHolder) { + PlayerVaults.scheduler().runAtEntity(player, task -> player.closeInventory()); + PlayerVaults.getInstance().getTL().locked().title().send(player); } } } @@ -143,7 +178,7 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo if (player.isSleeping() || player.isDead() || !player.isOnline()) { return false; } - int number; + final int number; try { number = Integer.parseInt(arg); if (number < 1) { @@ -154,49 +189,44 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo return false; } - if (checkPerms(player, number)) { - if (free || EconomyOperations.payToOpen(player, number)) { - VaultViewInfo info = new VaultViewInfo(player.getUniqueId().toString(), number); + if (!checkPerms(player, number)) { + PlayerVaults.getInstance().getTL().noPerms().title().send(player); + return false; + } - final Inventory inv = PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), (key) -> { - Inventory loadedVault = VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)); - if (loadedVault == null) { - PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); - } - return loadedVault; - }); + if (!free && !EconomyOperations.payToOpen(player, number)) { + PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); + return false; + } - if (inv == null) { - PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); - return false; - } + final String ownerKey = player.getUniqueId().toString(); + final VaultViewInfo info = new VaultViewInfo(ownerKey, number); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(ownerKey, number); - if (PlayerVaults.getInstance().getOpenInventories().get(info.toString()) != inv) { - PlayerVaults.debug("Vault for " + player.getName() + " was already opened by another thread."); - return true; - } + Inventory inv = VaultGate.withLock(gateKey, () -> + PlayerVaults.getInstance().getOpenInventories().computeIfAbsent(info.toString(), key -> + VaultManager.getInstance().loadOwnVault(player, number, getMaxVaultSize(player)) + ) + ); - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + if (inv == null) { + PlayerVaults.debug(String.format("Failed to open null vault %d for %s. This is weird.", number, player.getName())); + return false; + } - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); - return false; // inventory open event was cancelled. - } + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - if (send) { - PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); - } - return true; - } else { - PlayerVaults.getInstance().getTL().insufficientFunds().title().send(player); - return false; - } - } else { - PlayerVaults.getInstance().getTL().noPerms().title().send(player); + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); + return false; // inventory open event was cancelled. } - return false; + + if (send) { + PlayerVaults.getInstance().getTL().openVault().title().with("vault", arg).send(player); + } + return true; } /** @@ -219,7 +249,7 @@ public static boolean openOwnVault(Player player, String arg, boolean isCommand) * Open another player's vault. * * @param player The player to open to. - * @param vaultOwner The name of the vault owner. + * @param vaultOwner The name or UUID of the vault owner. * @param arg The vault number to open. * @return Whether or not the player was allowed to open it. */ @@ -236,9 +266,7 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar return false; } - long time = System.currentTimeMillis(); - - int number = 0; + final int number; try { number = Integer.parseInt(arg); if (number < 1) { @@ -247,41 +275,61 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar } } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return false; } - Inventory inv = VaultManager.getInstance().loadOtherVault(vaultOwner, number, getMaxVaultSize(vaultOwner)); - String name = vaultOwner; + final String holderKey = VaultManager.normalizeHolderKey(vaultOwner); + final VaultViewInfo info = new VaultViewInfo(holderKey, number); + final VaultGate.VaultKey gateKey = new VaultGate.VaultKey(holderKey, number); + + long time = System.currentTimeMillis(); + + Inventory inv = VaultGate.withLock(gateKey, () -> { + Inventory cached = PlayerVaults.getInstance().getOpenInventories().get(info.toString()); + if (cached != null) { + return cached; + } + Inventory loaded = VaultManager.getInstance().loadOtherVault(holderKey, number, getMaxVaultSize(holderKey)); + if (loaded != null) { + PlayerVaults.getInstance().getOpenInventories().put(info.toString(), loaded); + } + return loaded; + }); + + // Resolve a nice display name if possible + String displayName = holderKey; try { - OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(UUID.fromString(vaultOwner)); - name = offlinePlayer.getName(); + UUID uuid = UUID.fromString(holderKey); + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + if (op.getName() != null) { + displayName = op.getName(); + } } catch (Exception e) { // not a player } if (inv == null) { PlayerVaults.getInstance().getTL().vaultDoesNotExist().title().send(player); - } else { - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + PlayerVaults.debug("Opening other vault that does not fully exist.", time); + return false; + } - // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { - PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); - return false; // inventory open event was cancelled. - } - if (send) { - PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", name).send(player); - } - PlayerVaults.debug("opening other vault", time); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); - // Need to set ViewInfo for a third party vault for the opening player. - VaultViewInfo info = new VaultViewInfo(vaultOwner, number); - PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); - PlayerVaults.getInstance().getOpenInventories().put(player.getUniqueId().toString(), inv); - return true; + // Check if the inventory was actually opened + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); + return false; // inventory open event was cancelled. } - PlayerVaults.debug("opening other vault returning false", time); - return false; + if (send) { + PlayerVaults.getInstance().getTL().openOtherVault().title().with("vault", arg).with("player", displayName).send(player); + } + PlayerVaults.debug("opening other vault", time); + + // Track which vault this viewer is in + PlayerVaults.getInstance().getInVault().put(player.getUniqueId().toString(), info); + return true; } /** @@ -294,25 +342,27 @@ public static void deleteOwnVault(Player player, String arg) { if (isLocked()) { return; } - if (isNumber(arg)) { - int number = 0; - try { - number = Integer.parseInt(arg); - if (number == 0) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - return; - } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); - } - if (EconomyOperations.refundOnDelete(player, number)) { - VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); - PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); - } + if (!isNumber(arg)) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } - } else { + final int number; + try { + number = Integer.parseInt(arg); + if (number == 0) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } + } catch (NumberFormatException nfe) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(player); + return; + } + + if (EconomyOperations.refundOnDelete(player, number)) { + VaultManager.getInstance().deleteVault(player, player.getUniqueId().toString(), number); + PlayerVaults.getInstance().getTL().deleteVault().title().with("vault", arg).send(player); } } @@ -327,27 +377,37 @@ public static void deleteOtherVault(CommandSender sender, String holder, String if (isLocked()) { return; } - if (sender.hasPermission(Permission.DELETE)) { - if (isNumber(arg)) { - int number = 0; - try { - number = Integer.parseInt(arg); - if (number == 0) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - return; - } - } catch (NumberFormatException nfe) { - PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); - } + if (!sender.hasPermission(Permission.DELETE)) { + PlayerVaults.getInstance().getTL().noPerms().title().send(sender); + return; + } + if (!isNumber(arg)) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; + } - VaultManager.getInstance().deleteVault(sender, holder, number); - PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", holder).send(sender); - } else { + final int number; + try { + number = Integer.parseInt(arg); + if (number == 0) { PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; } - } else { - PlayerVaults.getInstance().getTL().noPerms().title().send(sender); + } catch (NumberFormatException nfe) { + PlayerVaults.getInstance().getTL().mustBeNumber().title().send(sender); + return; + } + + VaultManager.getInstance().deleteVault(sender, holder, number); + String display = holder; + try { + UUID uuid = UUID.fromString(holder); + OfflinePlayer op = Bukkit.getOfflinePlayer(uuid); + if (op.getName() != null) display = op.getName(); + } catch (Exception e) { + // The return instance here can be suppressed } + PlayerVaults.getInstance().getTL().deleteOtherVault().title().with("vault", arg).with("player", display).send(sender); } /** @@ -387,9 +447,9 @@ private record PlayerCount(int count, Instant time) { public static int countVaults(Player player) { UUID uuid = player.getUniqueId(); - PlayerCount count = countCache.get(uuid); - if (count != null && count.time().isAfter(Instant.now().plus(secondsToLive, ChronoUnit.SECONDS))) { - return count.count; + PlayerCount cached = countCache.get(uuid); + if (cached != null && Instant.now().isBefore(cached.time().plus(secondsToLive, ChronoUnit.SECONDS))) { + return cached.count; } int vaultCount = 0; for (int x = 1; x <= PlayerVaults.getInstance().getMaxVaultAmountPermTest(); x++) { diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java index 756b9d2..0dbf646 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultViewInfo.java @@ -29,6 +29,7 @@ public class VaultViewInfo { /** * Makes a VaultViewInfo object. Used for opening a vault owned by the opener. * + * @param vaultName UUID (string) or legacy key * @param i vault number. */ public VaultViewInfo(String vaultName, int i) { @@ -58,4 +59,4 @@ public int getNumber() { public String toString() { return this.vaultName + " " + this.number; } -} \ No newline at end of file +} From d17197e5e7b99bcd5dfdd3266f2e9e53703ddc83 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 19:38:58 -0400 Subject: [PATCH 06/11] Unschedule inventory opening since it's not necessary with how this is conditionalized; also causes saving breakage --- .../playervaults/vaultmanagement/VaultOperations.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index cebe69f..8053cfc 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -214,7 +214,7 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo return false; } - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + player.openInventory(inv); // Check if the inventory was actually opened if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { @@ -314,7 +314,7 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar return false; } - PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); + player.openInventory(inv); // Check if the inventory was actually opened if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { From 8e13815012d6333b34c341dbedc9b3e473dec06c Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 23 Aug 2025 19:43:21 -0400 Subject: [PATCH 07/11] Codestyle tweaks --- src/main/java/com/drtshock/playervaults/PlayerVaults.java | 1 + .../drtshock/playervaults/vaultmanagement/VaultManager.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/drtshock/playervaults/PlayerVaults.java b/src/main/java/com/drtshock/playervaults/PlayerVaults.java index af7f096..03c48a6 100644 --- a/src/main/java/com/drtshock/playervaults/PlayerVaults.java +++ b/src/main/java/com/drtshock/playervaults/PlayerVaults.java @@ -339,6 +339,7 @@ public void onEnable() { } } } catch (Exception ignored) { + // Ignored case/handler. } }, 1, 20 /* ticks */ * 60 /* seconds in a minute */ * 60 /* minutes in an hour*/); } diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java index 8e99dd2..6656edc 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultManager.java @@ -81,7 +81,9 @@ public static String normalizeHolderKey(String input) { try { UUID uuid = UUID.fromString(input); return uuid.toString(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + // Ignored case/handler. + } OfflinePlayer byName = Bukkit.getOfflinePlayer(input); if (byName != null && byName.getUniqueId() != null) { From 00ea5206399ecdadec8bb20faa42b0f0d61d6ee4 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Mon, 13 Oct 2025 01:54:45 -0400 Subject: [PATCH 08/11] Rebase --- pom.xml | 12 ++++++------ src/main/resources/plugin.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index ab86dd4..0a30407 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.drtshock PlayerVaultsX - 4.4.7 + 4.4.8 PlayerVaultsX https://www.spigotmc.org/resources/51204/ @@ -127,7 +127,7 @@ net.kyori adventure-text-serializer-gson - 4.24.0 + 4.25.0 compile true @@ -152,7 +152,7 @@ net.kyori adventure-text-serializer-legacy - 4.24.0 + 4.25.0 compile true @@ -169,7 +169,7 @@ net.kyori adventure-text-minimessage - 4.24.0 + 4.25.0 compile true @@ -186,7 +186,7 @@ dev.kitteh cardboardbox - 3.0.4 + 3.0.5 compile @@ -198,7 +198,7 @@ org.spigotmc spigot-api - 1.21.6-R0.1-SNAPSHOT + 1.21.9-R0.1-SNAPSHOT provided diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 7948698..8f4cc79 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,7 +5,7 @@ website: ${project.url} version: ${project.version} main: com.drtshock.playervaults.PlayerVaults softdepend: [Vault, Multiverse-Inventories, PlaceholderAPI] -api-version: 1.21.6 +api-version: 1.21.9 folia-supported: true commands: From 5a80264fd46e168600f0303e7edf0404e32ae054 Mon Sep 17 00:00:00 2001 From: Jonah Date: Mon, 27 Oct 2025 21:30:10 -0400 Subject: [PATCH 09/11] Add Malts converter (#20) --- .../playervaults/commands/ConvertCommand.java | 1 + .../converters/MaltsConverter.java | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/java/com/drtshock/playervaults/converters/MaltsConverter.java diff --git a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java index d6e0943..b6c88f9 100644 --- a/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java +++ b/src/main/java/com/drtshock/playervaults/commands/ConvertCommand.java @@ -44,6 +44,7 @@ public ConvertCommand(PlayerVaults plugin) { converters.add(new UniVaultsConverter()); converters.add(new XVaultsConverter()); converters.add(new FairyVaultsConverter()); + converters.add(new MaltsConverter()); this.plugin = plugin; } diff --git a/src/main/java/com/drtshock/playervaults/converters/MaltsConverter.java b/src/main/java/com/drtshock/playervaults/converters/MaltsConverter.java new file mode 100644 index 0000000..e7dca56 --- /dev/null +++ b/src/main/java/com/drtshock/playervaults/converters/MaltsConverter.java @@ -0,0 +1,60 @@ +package com.drtshock.playervaults.converters; + +import com.drtshock.playervaults.PlayerVaults; +import com.drtshock.playervaults.vaultmanagement.VaultManager; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.inventory.Inventory; + +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; + +public class MaltsConverter implements Converter { + + private static final String MALTS_API = "dev.jsinco.malts.api.MaltsAPI"; + + + @Override + public int run(CommandSender initiator) { + PlayerVaults plugin = PlayerVaults.getInstance(); + VaultManager vaultManager = VaultManager.getInstance(); + int convertedCount = 0; + + try { + CompletableFuture> allVaults = (CompletableFuture>) Class.forName(MALTS_API).getDeclaredMethod("getAllVaults").invoke(null); + Collection vaults = allVaults.join(); // Block thread I guess + + + for (Object vault : vaults) { + try { + UUID owner = (UUID) vault.getClass().getMethod("getOwner").invoke(vault); + int id = (int) vault.getClass().getMethod("getId").invoke(vault); + Inventory inventory = (Inventory) vault.getClass().getMethod("getInventory").invoke(vault); + + vaultManager.saveVault(inventory, owner.toString(), id); + convertedCount++; + } catch (ReflectiveOperationException e) { + plugin.getLogger().severe("Failed to convert a vault: " + e.getMessage()); + } + } + initiator.sendMessage("Converted " + convertedCount + " vaults from Malts."); + + } catch (ReflectiveOperationException e) { + initiator.getServer().getLogger().log(Level.SEVERE, "Failed to convert vaults", e); + return -1; + } + return convertedCount; + } + + @Override + public boolean canConvert() { + return Bukkit.getServer().getPluginManager().isPluginEnabled("Malts"); + } + + @Override + public String getName() { + return "Malts"; + } +} From 1643ae669c2dda566f9594c759fc4539b32747a8 Mon Sep 17 00:00:00 2001 From: r00tb33rman Date: Wed, 29 Oct 2025 18:50:42 -0400 Subject: [PATCH 10/11] Perform openInventory at the player --- .../vaultmanagement/VaultOperations.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java index 8053cfc..c844cd9 100644 --- a/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java +++ b/src/main/java/com/drtshock/playervaults/vaultmanagement/VaultOperations.java @@ -214,13 +214,15 @@ private static boolean openOwnVaultE(Player player, String arg, boolean free, bo return false; } - player.openInventory(inv); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory) { PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); PlayerVaults.getInstance().getOpenInventories().remove(info.toString()); return false; // inventory open event was cancelled. + } else { + player.getOpenInventory().getTopInventory(); } if (send) { @@ -314,12 +316,14 @@ public static boolean openOtherVault(Player player, String vaultOwner, String ar return false; } - player.openInventory(inv); + PlayerVaults.scheduler().runAtEntity(player, task -> player.openInventory(inv)); // Check if the inventory was actually opened - if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory || player.getOpenInventory().getTopInventory() == null) { + if (player.getOpenInventory().getTopInventory() instanceof CraftingInventory) { PlayerVaults.debug(String.format("Cancelled opening vault %s for %s from an outside source.", arg, player.getName())); return false; // inventory open event was cancelled. + } else { + player.getOpenInventory().getTopInventory(); } if (send) { From 7165f4189d8b42c655316295f0884f93474a89ad Mon Sep 17 00:00:00 2001 From: RootBeer Date: Tue, 23 Dec 2025 16:00:22 -0500 Subject: [PATCH 11/11] Rebase --- src/main/resources/plugin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5fb647b..ab264e8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ version: ${project.version} main: com.drtshock.playervaults.PlayerVaults softdepend: [Vault, Multiverse-Inventories, PlaceholderAPI] api-version: 1.21.10 +folia-supported: true commands: pv: